“In my previous blog, I delved deeper into the meaning of Event-Driven Architecture. This blog marks the second part of a series dedicated to exploring Event-Driven Architecture. It is important to note that the viewpoints expressed in these blogs are my own and are subject to ongoing discussions within our community”. Our Integration Architect Joost sets the scene!
Before we delve into the different event types, I want to emphasize that these blogs will not focus on the structure or content of an event itself. This issue stands alone, distinct from our present topic. However, classifying your events can significantly streamline these discussions.
My main argument is the importance of classifying your events by their role in your architecture. With this premise, we can distinguish two main types of events.
These events aim to drive forward a specific business process or workflow. They serve as signals that a significant event has occurred within a process, thereby cueing the next component or actor to perform its task. This is, in my view, an excellent illustration of implementing a business process using choreography.
Side Note: Event Sourcing
Event Sourcing can also be classified as a type of event. However, I've excluded it from this discussion because it's more associated with the internal architecture of a specific component within a given solution. It's a method for capturing and managing state within a particular component. In this blog, my focus is more on the overarching architecture of a business solution, where the relevance of event sourcing is not as pronounced.
The purpose of these events is to propagate state information to other components within a specific business solution. This design pattern has gained increased attention with the rise of microservices, enhancing the robustness of such architectures.
By transferring state/data from a master microservice to other microservices, the system can continue processing even if the master microservice becomes temporarily unavailable. This approach also improves the performance of certain operations, as the data becomes locally accessible, effectively functioning as a local cache.
When designing an event-driven system, it's crucial to consider this distinction, as it influences the requirements and design of these events. I would argue that keeping them separate is always a good idea. This approach can make your architecture more adaptable to changes in future requirements (see the following example below).
Again, my focus isn't on the content of the events, like notification events or state-carrying events, as outlined by M. Fowler (https://martinfowler.com/articles/201701-event-driven.html). My argument revolves around the idea that the discussion should be guided by the purpose of the event—whether it's business-oriented or data-driven—and there are valid reasons for both types to carry a certain degree of state information.
This simple example is intended to illustrate my point.
Consider a straightforward ordering process involving three components within a business solution:
- Ordering Component: Captures the customer's order.
- Payment Component: Facilitates the payment for outstanding orders.
- Warehouse Component: Manages the shipping of the order.
Here's how the process unfolds: when an order is placed, the system triggers a payment request. Once the payment is successfully processed, the order is then shipped by the warehouse.
To maintain system robustness, we aim to prevent the entire process from breaking down if any of its components become unavailable.
The diagram above outlines our solution architecture. We've chosen two business events: 'Order Placed' and 'Payment OK'. These events carry limited information, dictated by what's needed needed to move the process forward. The 'Order Placed' event only carries an ID, whereas the 'Payment OK' event carries an ID and additional context information, such as the order ID. In addition, we have a data event, 'Order Data', that contains the full state of an order, available to any component requiring a copy of the data. In our scenario, the Payment and Warehouse components consume this event to ensure system resilience, as they need this data to complete their functions.
Those different components also listen to business events to determine when to trigger specific functions. Could we have included all data within the business events? Certainly, but remember that the goal of these business events is not primarily data propagation. I advise against mixing these two uses, as they address completely different problem scopes.
This distinction becomes more apparent when we incorporate changes. Let's assume a new requirement to incorporate a 'marketing' component into the process. We plan to implement a feature that sends a discount coupon for the next order to customers who place orders over €100 for a specific product.
In the example above, nothing needs to change in the existing architecture to accommodate this new functionality. The new functionality can be implemented by extending the current system. The new marketing component will listen to the “order placed” event. Depending on its resilience requirements, it either listens to the 'Order Data' event an
d has all the information locally, or it retrieves data synchronously from the order component as shown here. Chances are high that the data event contains all necessary order information since that's its purpose. The business event also carries enough information to continue the processing.
If both these events were merged into one, this extension wouldn't be as straightforward. There would likely be endless debates over what state the single event should carry to keep it lean and efficient. Why? Because it would have to serve two masters: controlling a business process and propagating state data.
Certainly, this architecture may seem more complex, but such complexity is necessary in an event-driven system to prevent it from becoming too confusing quickly.
Therefore, I'm not entirely convinced that an event-driven system is always the best solution. The first step should always be to examine the specific requirements of the system you're building and then develop an architecture that meets those needs. But that's architecture 101, isn’t it?
Ultimately, as we all understand, designing a system always involves making trade-offs. The decision on the path to follow rests with you. My hope is that this blog has provided you with additional insights to help you make crucial trade-offs effectively.