Event-driven architectures (EDA) are an architecture style that uses events and asynchronous communication to loosely couple an application’s components. Event-driven architectures can help you boost agility and build reliable, scalable applications.
Before getting started with EDA it is important to understand a key concept that is tight vs loose coupling.
Coupling is the measure of dependency each component of an application has on one another. For example, a components need to call another in order to complete its task and the order in which things are called matter.
Tightly coupled systems can be particularly effective if the application has few components or if a single team or developer owns the entire application.
However, when components are more tightly coupled, it becomes increasingly likely that a change or operational issue in one component will propagate to others. For complex systems with many teams involved, tight coupling can have drawbacks. When components are tightly interdependent, it can be difficult and risky to make changes isolated to a single component without affecting others. This can slow down development processes and reduce feature velocity.
Tightly coupled components can also affect an application’s scalability and availability. If two components depend on one another’s synchronous responses, a failure in one will cause the other to fail. These failures can reduce the application’s overall fault tolerance.
Reducing coupling is the act of reducing interdependency between components and the awareness each component must have of one another. Event-driven architectures achieve loose coupling through asynchronous communication via events. This happens when one component doesn’t need another to respond. Instead, the first component may send an event and continue without impact should the second component delay or fail.
When communicating with events, components only need to be aware of the independent events. They don’t require knowledge of the transmitting component or any other components’ behavior (e.g., error handling, retry logic). So long as the event format remains the same, changes in any single component won’t impact the others. This allows changing an application with less risk.
When asynchronous events abstract components from one another, complex applications become more resilient and accessible. For example, in this case we have event producers and event consumers all connected using an event broker. There are many patterns like this that we are going to see during this series.
Core benefits of EDA
A shift in mindset is required when building an event-driven architecture because of the unique characteristics and considerations of asynchronous systems. Yet, this architecture style offers important benefits for complex applications:
- Build and deploy applications independently in loosely coupled applications. Development teams working on individual services have fewer dependencies. Changing one service will have less risk of impacting others.
- Build new features using events without changing existing applications. (Extend your applications) Since components emit events, event-driven architectures are easily extensible. Events can also be analyzed for business reports and audits.
- Scale and fail components independently in loosely coupled components. Applications with loosely coupled components have fewer single points of failure, as well as increased resiliency.
Key concepts of event-driven architectures
An event is the signal of a changed state, such as an item in a shopping cart or a credit card application. Events occur in the past (e.g., “OrderCreated” or “ApplicationSubmitted”) and are immutable, meaning they can’t be changed. This helps in distributed systems because there are no changes across components to keep in sync. Events are observed, not directed. A component that emits an event has no particular destination nor awareness of downstream components that may consume the event.
Event-driven architecture possesses these key components:
- Event producers publish events. Some examples include front-end websites, microservices, Internet of Things (IoT) devices, AWS services, and software-as-a-service (SaaS) applications.
- Event consumers are downstream components that activate in events. Multiple consumers may be found in the same event. Consuming events includes starting workflows, running analyses, or updating databases.
- Event brokers mediate between producers and consumers, publishing and consuming shared events while mitigating the two sides. They include event routers that push events to targets and event stores from which consumers pull events.
Point-to-point messaging is a pattern in which producers send messages typically intended for a single consumer. Point-to-point messaging often uses messaging queues as its event broker.
Queues are messaging channels that allow asynchronous communication between a sender and receiver. Queues provide a buffer for messages in case the consumer is unavailable or needs to control the number of messages processed at a given time.
Messages persist until the consumer processes them and deletes them from the queue. One consumer can consume each message. If we need that the message is consumed by multiple recievers you need to use another pattern.
If we look at AWS - Amazon SQS and Amazon MQ are used as managed queue services. And Lambda functions can get triggered when a new message is in the queue.
Dead letter queues
Dead letter queues are queues that you configured where messages that cannot be sent in the point to point pattern cannot be sent.
SQS and other queue service or message brokers will retry resending the message as many times as configured. But in some point if there is a problem with the target or with the event the message won’t be able to send.
If you don’t configure a place where the messages are stored, then the message will be discarded. That is why DLQ are important. This are a on-failure destination for all your messages. Later you can reprocess and analyse all the messages in the queue.
Pub/sub messaging is a way in which producers send the same message to many consumers. Whereas point-to-point messaging usually sends messages to just one consumer, publish-subscribe messaging allows you to broadcast messages and send a copy to each consumer.
The event broker in these models is frequently an event router. Unlike queues, event routers rarely offer persistence of events.
One type of event router is a topic, a messaging destination that facilitates hub-and-spoke integrations. In this model, producers publish messages to a hub, and consumers subscribe to the topics of their choice.
Another type of event router is an event bus, which provides complex routing logic. While topics push all sent messages to subscribers, event buses can filter the incoming flow of messages and push them to different consumers based on event attributes.
Using streams, or continuous flows of events or data, is another method of abstracting producers and consumers. In contrast to event routers, though comparable to queues, streams typically require consumers to poll for new events. Consumers maintain their unique filtering logic to determine which events they want to consume while tracking their position in the stream. Event streams are continuous flows of events, which may be processed individually or together over time.
Data streams vs event streams?
Data streams differ from event streams in that they always interpret data. In this model, individual data points, or records, aren’t independently useful. Data streaming applications are often used to either persist the data after an optional enrichment or to process the data over a preset time period to derive real-time analytics.
The last of the patterns is the event bus. And here our event router has rules that send the events from the consumers to the producers of events.
Everything about Amazon EventBridge: blog.marcia.dev/event-driven-applications
Everything about AWS Step Functions: blog.marcia.dev/orchestrated-apps