Event-driven architecture (EDA)

A monolithic application typically has a single compute code base accessing a single relational database with Atomicity, Consistency, Isolation, and Durability (ACID) semantics. As a result, typically, multi-table updates triggered by external requests are easy to do by issuing the update statements under a transaction (Chapter 8, Modeling Data, covers these concepts in detail). When this monolith gets decomposed into microservices, each microservice gets its own database (to avoid coupling). Distributed transactions are possible, but avoidable due to following reasons:

  • They can take much longer to converge and are more error-prone than transactions against a single database
  • All microservices may not have a relational databasethey pick what suits their use cases best

Event-driven architecture (EDA) promotes an architectural paradigm where behavior is composed by reacting to events. Here, events imply a significant change in statefor example, when a customer checks in their hotel, the state of that object changes from booked to consumed. This may trigger multiple reactions in the system, from reconciliation of payment with the Seller (hotel owner) to sending an email to the customer asking them to provide feedback.

A schematic of a typical web application that uses EDA is shown here:

Here, the messaging bus serves as the event delivery mechanism. Services listen on Topics in the message queue, consume new messages, and then reacts to them. Messaging is covered in detail in Chapter 6, Messaging. The main advantage of this paradigm is that components are loosely coupledthey don't need to explicitly refer to each other. If the behavior needs to be extended so that new systems can react to events, the original plumbing and existing consuming components are unaffected.

Another application of this pattern is to prepare Materialized Views. The context here is the followinguser-request fulfilment is enabled by multiple distributed services, however the system needs to present a view about the overall request. For example, in the hotel booking flow, the user would like to see their booking as it progresses through various states, such as INITIAL, PAYMENT_MADE, and BOOKING_CONFIRMED, along with ancillary details such as the check-in time at the hotel. All this data is not available with one service, thus to prepare the view, one naive approach might be to query all the services for the data and compose the data. This is not always optimal since the service usually models data in the format that it requires it, not this third-party requirement. The alternative is, in advance, to pre-populate the data for the view in a format best suited to the view. Here is an example:

This pattern allows the decoupling promised by microservices, while still allowing the composition of rich cross-cutting views.

There are still things to consider there in terms of consistencyfor example, what happens if the Booking Service updates the database, but before it can send out an event on the messaging bus it crashes? For the preceding materialized view use case, you can think of building reconciliation strategies, but the solution won't scale. The trick is to have an Event table in the local microservice database, which stores the intent to send a message. The update to the main table and this Event table can be atomic (using transactions). There is another background worker/thread that takes data from the Event table and actually fulfils the intent of sending the message:

The preceding approach is generally called the event sourcing paradigm. The main philosophy behind the pattern is to model data operations as a sequence of events in an append-only log, rather than the absolute values. The current state is composed only when needed and is easy to do—just take the latest update, for example. Besides allowing for the efficient computation of varied goals, another key advantage of the event sourcing model is that, since it persists events rather than domain objects, it mostly avoids the object‑relational impedance mismatch problem (https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch).

In case the application cannot be rewritten to source events, an alternative approach is to mine the database updates as sources of events and then use the updates as triggers to generate messages.

There is a tradeoff in terms of increased complexity with event sourcing.

The CQRS (Command and Query Responsibility Segregation) pattern needs to be followed. CQRS mean splitting application logic into two parts: the command side handles data updates, and the query side handles reads. The command side of the code does not care about the queriesit just sends the changes of the data as events. On the read side, these events are curated in the manner best suited for read (materialized views).

The consistency of different/distributed derived results is not guaranteed at all times. However, eventually, all derived results will be consistent. Thus, the applications should be able to handle eventual consistency (the BASE consistency model described in the Consistency subsection).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset