Why Event Sourcing is the antipattern for microservices interaction

Hello again. In March, OTUS launches the next stream of the Software Architect course . In anticipation of the start of the course, we have prepared a translation of useful material for you.




Recently, event-driven architectures (Event-driven architectures) and, in particular, Event Sourcing (the generation of events) have become widespread. This is driven by the desire to create sustainable and scalable modular systems. In this context, the term “microservices” is often used. In my opinion, microservices are just one of the ways to implement a “ Bounded Context ”. It is very important to correctly define the boundaries of the modules, and Strategic Design , described by Eric Evans in Domain Driven Design, helps in this . It helps you identify / detect modules, boundaries (“restricted context”) and describe how these contexts relate to each other (context map, ContextMap).

Domain events as the basis of a single language


Although this is not explicitly indicated in Eric Evans' book, domain events contribute very well to DDD concepts. Practices such as Event Storming Alberto Brandolini shift the focus of events from the technical to the organizational and business level. Here we are not talking about user interface events, such as clicking on a button (ButtonClickedEvent), but about domain events that are part of the subject area. They are talked about and understood by experts in the subject area. These events are the primary concepts and help to form a single language ( ubiquitous language ), with which all participants (subject matter experts, developers, etc.) will agree.

Domain events used to communicate between contexts


Domain events can be used to interact between restricted contexts. Suppose we have an online store with three contexts: Order (delivery), Delivery (delivery), Invoice (account).

Consider the event “Order Accepted” in the context of Order. The Invoice context, as well as the Delivery context, are interested in tracking this event, as this event triggers some internal processes in these contexts.

The Myth of Weak Connectivity


Using domain events helps develop loosely coupled modules. Individual modules may not be available temporarily. But for a domain event, it is absolutely not important whether they are available or not, since the event only describes what happened in the past. Other modules decide when to process the event. You get a flexible system by default.

In addition to time decoupling, domain events give you another advantage: the order context does not need to know that the context of the invoices and delivery listens to its events. In fact, he does not even need to know that these contexts exist.

It's great, but the difficulty lies in deciding what data to store in the event?

The simple answer: Event Sourcing!


Events are useful, so why not use them to the maximum. This is the main idea of Event Sourcing . You store the state of the unit not through updating its data (CRUD), but through the use of an event stream.
Besides the fact that you can play events and get status, there is another feature of Event Sourcing: you get a complete audit log for free. Therefore, when such a log is required, be sure to pay attention to Event Sourcing when choosing a storage strategy.

Event Sourcing is just a storage tier


It may seem strange to you that I immediately switched from domain events to storage, since, obviously, these are concepts of different levels.

... and here's my point: Event Sourcing is a local solution used in only one limited context! Event Sourcing events should not be pulled out into the outside world! Other limited contexts do not need to know about how each other's data is stored, and therefore it doesn’t matter if some context uses Event Sourcing.

If you use Event Sourcing globally, then you disclose your storage level.

The data storage method becomes your public API. Each time you make changes to the storage tier, you will have to deal with a change in the public API.

I am sure everyone will agree that it is bad when various limited contexts share data in a (relational) database due to the resulting connectivity. But how is this different from Event Sourcing? Nothing. It doesn’t matter if you use shared events or shared tables in the database. In both cases, you share the storage details.

There is an exit


I still argue that domain events are ideal for interacting between limited contexts, but these events should not be related to the events that are used for Event Sourcing.

The proposed solution is very simple: no matter what approach you use to store data (CRUD or Event Sourcing), you publish domain events in the global event store. These events represent the public API of your context. When using Event Sourcing, you store Event Sourcing events in your local store, accessible only for this limited context.

freedom of choice


Having separate domain events in the public API allows you to model them flexibly. You are not limited to a model that is predefined by Event Sourcing.

There are two options for working with “real-world events”: an Open Protocol Service and a Public Language (Open Host Service, Published Language) or a Customer / Supplier.

Service with an open protocol and a public language (Open Host Service, Published Language)


Only one domain event is published that contains all the data that other limited contexts may need. In DDD terminology, this can be called an Open Host Service and a Published Language.



The onset of the real-world event “Order Accepted” leads to the publication of one OrderAccepted domain event . The payload of this event contains all the order data that other restricted contexts might need ... so hopefully the Invoice and Delivery contexts will find all the information they need.

Customer / Supplier


For each consumer, separate events are published. It is necessary to coordinate the models of each event with only one consumer; it is not necessary to determine a common shared model. DDD calls this relationship Customer / Supplier.



The occurrence of real-world events “Order accepted” leads to the publication of individual events for each of the consumers: InvoiceOrderAcceptedand DeliveryOrderAccepted. Each domain event contains only the data that is necessary for the recipient context.

I do not want to discuss the pros and cons of these approaches now. I just want to draw attention to the fact that you can choose the number of domain events and the data that they store.

This is an advantage that you should not underestimate, because you can decide how to develop the API of your limited context without being bound to Event Sourcing events.

Conclusion


Exposing storage parts is a well-known anti-pattern. Speaking of storage, we primarily think about database tables, but we saw that the events used for Event Sourcing are just another way to store data. Therefore, giving them out is also an anti-pattern.


Translation: “a good developer like a werewolf is afraid of silver bullets.”

Event Sourcing is a powerful approach if used correctly (locally). At first glance it seems that for event-oriented architectures this is a silver bullet, but if you look closely, you can see that this approach can lead to a strong connection ... which of course you do not want.

References


In addition to my personal experience, I received a lot of inspiration from various articles and conferences. I would like to mention the presentation by Eberhard Wolff “Event-based Architecture and Implementations with Kafka and Atom” (event architecture and implementation using Kafka and Atom). Especially about Event Sourcing and what events are , which is very relevant in the context of this post. The online store example was also inspired by this talk.

If you want more information, you can refer to these resources:


: « : ».

All Articles