Definition and Context
Event-driven programming is structured according to the Hollywood principle “Don’t call us, we call you”. Event-driven programming is a paradigm of system architecture where the logic flow within the program is driven by events such as user actions, messages from other programs, GPS signals or hardware (sensor) inputs.
Events govern the overall flow of program execution, and the application runs and waits for events to occur. When an event is triggered, the application code, which is listening to the events, responds by running a specific handling function (callback function).
Oftentimes, a program has to deal with external events through inputs/outputs (I/O). I/O and event management are the foundations of any computer system: reading or writing from storage, handling touch events, drawing on a screen, sending or receiving information on a network link, and so on.
Characteristics of event driven programming
Central to event-driven programming is the stream of data or events. It is intended for push (reactive) but can be used for pull as well. It is lazy rather than eager and it is usually used asynchronously. And it can represent 0, 1, many, or infinite values or events over time. Let’s break this statement down to its basic parts.
Declarative vs. Imperative
Most of us write imperative applications, where statements are executed in a specific order to change the application state. The code is executed and we arrive at a final state. After the state is calculated, the state does not change when the underlying factors do.
On the other hand, event-driven programming is about the propagation of change. It is also referred to as declarative programming, where we express our intent but the application’s state is dynamically determined by changes of underlying factors. Event-driven programs can be built using imperative techniques, like callbacks. This may be fine for a program that has a single event. However, for applications where hundreds of events are happening, this could easily lead to callback hell. We could have numerous callbacks relying on one another, and it would be really difficult to figure out which ones were being executed.
As a result, we require a new set of abstractions that enable us to seamlessly build asynchronous, event-driven interactions across a network boundary. There are libraries in different languages, like Java, that provide us with these abstractions. These libraries are referred to as Reactive Extensions.
Push vs. Pull
Reactive programs can be classified as push-based and pull-based. The pull-based system waits for a request from the subscriber to push the data. This is the classic example where the data source is actively polled for more information. At the code level, this employs the iterator pattern, and Iterable<T> interface is specifically designed for such scenarios that are synchronous in nature since the application can block while pulling data.
On the other hand, a push-based model aggregates events and pushes through a series of listeners to achieve the computation. In this case, unlike the pull-based system, data and related updates are handed to the subscriber from the source (Observable sequences in this case). This asynchronous nature is achieved by not blocking the subscriber, but rather making it react to the changes. As you can see, employing this push pattern is more beneficial in rich UI environments where you wouldn’t want to block the main UI thread while waiting for some events. This becomes ideal, thus making event-driven programs responsive.
Async vs. Sync
Event-driven programming could be considered asynchronous since components involved in the system don’t communicate directly, or waiting for a response from each other. The services that participate in the communication, simply publish a message and then moves on. Whoever is interested can listen to the event, whenever is useful for them.
Asynchronous means no waiting time. The caller function does not wait for a response from the called service; it continues doing its next task.
Synchronous means waiting time. The caller should wait for a response from the invoked service and it cannot continue doing its next task. The caller service should wait until the invoked service finishes its job and returns results (success or failure).
Asynchronous and nonblocking I/O is about not blocking threads of execution and it’s often more cost-efficient through more efficient use of resources. It helps minimize congestion on shared resources in the system, which is one of the biggest impediment to scalability, low latency, and high throughput.
Lazy vs. Eager
Event-driven programming requires a different way of thinking than conventional imperative programs. An event-driven program is not in control while waiting for an event, and in fact, it’s not even running. The program is called to process the event, only once the event is triggered, and then it quickly drops the control again. The event-driven program does nothing until it is required to, and this allows code structures to be composed. Another advantage is that the application can wait for many events in parallel, and therefore the system remains responsive to all the events it needs to handle.
Bibliography:
- Tomasz Nurkiewicz, Ben Christense: Reactive Programming with RxJava, O’Reilly Media, October 2016
- https://www.researchgate.net/publication/259527221_Practical_UML_Statecharts_in_CC_Event-Driven_Programming_for_Embedded_Systems