Hero Image

gravity9 System Architecture Guide: Event Driven Architecture

 

21 Mar 2024 | Julio Castellanos

System Architecture Guide: Exploring Event-Driven Architecture (EDA)

Architecture is a vital aspect of all systems, and applications, serving as a blueprint for engineers which defines behaviour and structure. There are numerous architectural patterns out there and they can be used on different levels of a system. In fact, a single system can use multiple architectural patterns!

As a digital consultancy, at gravity9 we have a rich history and heritage of development, picking the best architecture for the job.

In this series of articles, we’ll introduce some of the most popular system architecture around. We’ll look at why they’re popular, where they’re useful and where they’re less useful.

 

What is an Event?

An event (as defined in event driven architecture) is every action that occurs in a system.

Using the analogy of a company which distributes apples, some example events might be:

  • Apple order received.

  • Apple delivered.

  • Apple inventory changed.

  • An apple got rotten.

An event is not restricted by what should happen after that event takes place, and so is not concerned about the future of the system (even if it triggers a change in another part of said system). The event only cares to inform about something that already happened and usually uses the past tense (updated, changed, created, received, delivered, etc). If there’s an event in our system not using the past tense, some serious questions should be asked about the event and how it interacts with the architecture.

Some examples of what should not be considered an event are:

  • Decision to ship product – (it’s a non-past-tense entity and is not directly related to the execution of an action).

  • User searched the product – (it does not add value to a possible chain of events).

 

Event-Driven Architecture.

The concept of event driven architecture, commonly known as “EDA”, is a system design architecture where everything flows around events. Every component can produce or consume events and, ideally, the only means of communication with other components of the system is through events.

There are two important concepts central to EDA:

  • Producer: A component that produces an event (it can be one or many events).

  • Consumer: A component that consumes an event, and executes an action based on that event.

Every component of the system can produce and consume multiple events. A business process can be defined through a chain of events. Continuing with the example of the apple distribution system, a view of an EDA for the system could look something like this:

 

 

The business process that we want to describe here is the delivery of an apple, using events for the interaction between components.

The steps (in terms of business language) could be described like this:

  • apple-requested: A customer approached us with their intent of acquiring an apple.

  • apple-order-confirmed: The system accepted the request for an apple and created an internal order to keep track of the progress.

  • apple-order-processed: The inventory system recognized the order and is preparing the delivery.

  • apple-order-dispatched: The apple was sent to the delivery service.

  • apple-delivered: The apple was delivered to the customer.

Referencing the image above, C1 through C4 are components of the system with different responsibilities, each one of them being a consumer and a producer (at the same time).

As an example: C4 could be our delivery service, only responsible for getting an apple delivered (without caring where it came from). It would consume the apple-order-dispatched event and know that it should trigger a new delivery and produce the apple-delivered event. This in turn would notify the success of the operation and inform any other part of the system that’s interested in the deliveries made (reporting would be a good example).

This might seem overcomplicated, if you just wanted to deliver an apple, but that complexity can be very useful…

 

Event Driven Architecture.

We know the abstract of what an event is now, how to use them, and how they can interact.

However, where do we get them from? In a very complex business domain, it can be challenging to define all the events that will be used in a system. Sometimes there will be conflicts between them, or operations that can be achieved by observing multiple events.

 

Before the events, there were the domains!

 

Before considering event interactions, it’s best to review system domains. If there is good design of domains and subdomains, interactions may (and should) come naturally and be self-evident.

There are many approaches to good domain segregation, what’s important is that it represents the system being worked on. At gravity9 we find that Domain Driven Design (DDD) works particularly well – its strengths are an article on their own – but what’s important is that DDD focuses on finding bounded contexts, which we can think of as domains and subdomains for the purposes of building EDA.

The strategy to find bounded contexts can be summarised as:

  1. A bounded context represents a specific area within the overall problem domain (where a particular model or language applies consistently).

  2. Different parts of a system may have different meanings for the same terms, and a bounded context defines explicit boundaries within which those terms have specific meanings.

Bounded contexts help manage complexity by breaking down a large, complex domain into smaller, more manageable parts. Once we have those parts, EDA comes into action, helping communicate those bounded contexts using events!

 

Event-Driven Architecture Advantages. 

Imagine our apple shop gets one request every minute to deliver an apple – why bother with EDA? – a single person could easily meet this request.

Now imagine that shop becomes famous and gets one thousand requests every second to deliver an apple.

This is where the biggest advantages of EDA start to shine.

  • Loose Coupling Between Components: A producer does not have to know its consumers – meaning additional consumers can be added as the system grows, existing ones rewritten or removed – and these actions will never affect the producer of the event.

  • Temporal Decoupling: The components of the system can have their own time to finish operations (and waiting could result in a long time).
    For example, an order receiver system would be very fast at registering the order in a database and notifying the inventory service that an apple is requested, but the inventory service will take more time to calculate the availability of apples and where best to dispatch them from. If the order receiver waits for the inventory service to respond, it will be hard to keep up as further orders come in. Instead, it can generate the event, forget about it, and return a quick response to the user: “Your order with number xxx has been received, we are working to process it“.

  • Scalability: It becomes easier to scale individual components of a system, focusing where that investment is needed most based on different component needs.

  • Extendibility: Let’s say that we need a new component on the system, to generate reports on the number of apples processed and delivered. Instead of modifying every component to also send information to this new component, the information can be retrieved from the events already published.

When deciding whether to use Event-Driven Architecture, the choice often comes down to complexity. If the system is complex enough or will get very complex at some point due to business evolution, it can be a very powerful solution. If not? Other architectures may be preferable.

 

Event-Driven Architecture Disadvantages.

EDA is not a solution for every complex problem, and there are some disadvantages to its use.

  • Latency: Returning to our example, imagine that there’s a new business requirement: “Every apple request has to be delivered within 5 seconds”. With EDA, the communication time between components will likely be more than the time required to return the apple. If system performance is critical, EDA may not be the best choice.

  • Complexity: Dealing with events adds another layer to the system; an intermediary between components that must also be maintained, monitored, and operated as with all other components. If the business use case is simple, EDA may needlessly complicate the system and unnecessarily add to resource demands.

  • Operational Overhead: If something goes wrong (and an apple is not delivered), finding the component responsible for the failure will now take more time due to added complexity. While not impossible to deal with, a good logging strategy will be needed between components to correctly diagnose any issues.

  • Ordering Challenges: If we get a request to deliver “x” apple before “y” apple, another component must be added to ensure events are processed in order. Messaging systems provide delivery guarantee, but not ordering guarantee. This – again – adds complexity.

  • Error Handling and Rollbacks: When you have a transaction to a database, it doesn’t matter how complex it is, you can roll it back if something goes wrong with a single operation. In EDA, however, it doesn’t work so easily. Since systems are isolated, it’s likely that different components have different mechanisms to persist the information in a database.
    That means in the case of an error in one of the components in the chain of events, we will have to implement a mechanism to let all other components in the chain know that something went wrong, and they have to rollback their initial operation. There are options to deal with this problem, like the Saga pattern, but that also adds complexity. For a system that has large chains of events and lots of connected components, rolling back can be a serious problem!

While there are benefits to EDA, clearly there are also drawbacks, and these must be weighed up against the specifics of your own system when deciding the value of Event-Driven Architecture.

 

How Does gravity9 Work with Event-Driven Architecture? 

gravity9 has employed Event-Driven Architecture for several clients, each with its own specifics and complexities. For the purposes of this article, our client names are redacted.

Legacy Integration:

In one notable challenge, gravity9 was asked to integrate a number of legacy systems (including a system of record that’s vital to the company’s functioning). To be able to meet these requirements while keeping said systems isolated (as was necessary) at the same time, EDA with the adapter pattern was implemented.

This can be visualised as follows:

The events that take place here are:

  • legacy-system-1-modified and legacy-system-2-modified: The legacy system was modified, so the adapter observes the change, adapts to our system, and lets our system know via the event broker.

  • system-modified: Our system was modified, we let the adapter know via the event broker, so it can transform the event into the legacy system format and apply the modification. In the real-world scenario, it is not a single event, but thousands of different events for different domain specific business situations.

Although we focus here on events generated by the adapters, this doesn’t factor in the complexity of creating the adapter itself. Managing interactions with legacy systems can be a big challenge on its own! We may discuss this complexity in a future article, but it is not critical to this EDA focussed piece.

What’s important here is that the integration of legacy systems worked well thanks to Event-Driven Architecture.

Sub-Domain Interactions:

Event-Driven Architecture works particularly well with a microservices implementation where we are looking to orchestrate business events which mean a specific action for each one of the microservices. The shape of EDA events depends very much on the business domain.

In the case of one client, a leading operator within the rental property domain, this meant following the journey of a customer applying to rent a property.

We created an orchestrator component called service-application which provided useful consistency and observability of the journey. It observed events from several subdomains related to a customer interacting with an application, managed the internal state of the application and notified other subdomains about that application state.

This is how the basic journey, and interactions can be visualised:

We can see how other subdomains use events to notify subscribers about their operations, and how the application-service observes those events and progresses a customer application through its stages, publishing other events as it does so.

This is great for isolation of concerns. For example, the application service does not have to understand the complexity of how a payment works, as that is a different subdomain with its own interactions with subdomains and external systems to ensure the payment is processed. All we care about (from the application service point of view) is that a payment was completed – that is the trigger to move an application forward to the next stage.

Thanks to Event-Driven Architecture, the client was able to enjoy increasingly complex systems, and system interactions, with robust back-end functionality and front-end usability.

 

Is Event-Driven Architecture Right For YOU?

A popular way to tackle large, complex problems is to break them down into smaller, easier to solve ones, and Event-Driven Architecture is a great way to tackle this where system architecture is concerned.

The decision of what domains to use – and what events will help communicate the different domains – is critical. If you’re keen to implement EDA then designing proper interactions is so important, as too few interactions and you could have more of a difficult-to-manage Monolith architecture. Implement too many or unnecessary interactions and you risk collisions of events across the board!

EDA has significant strengths, and potential pitfalls too. It’s certainly not “one size fits all”.

As we’re seeing with so many architecture options, the right choice may depend entirely on your individual use-case. Why not check out other articles in our architecture series to learn more about your options, or get in touch for a more tailored review of your options?