The transactional CRUD update-in-place approach has served most enterprise use cases well for decades. CRUD can still be a reasonable option within a microservice that owns its data exclusively or for services that act as endpoints where data is mainly read, such as an email service.
As part of the trade-off for achieving reactive characteristics, complexity arises when the data needs to be shared among multiple services. You can’t easily do joins across services to get a consistent view of the data. One solution is to use an additional event stream to propagate events to a third-party service. The third-party service can do joins of information from multiple services and satisfy read-only queries. This avoids tight coupling resulting from trying to enforce consistency across microservices.
Transactions can’t span hosts without coordination. Distributed transactions incur high latency with increased possibility of failures — the opposite of microservice goals. In addition, operations that block all the way down to the database often do not take full advantage of multi-core architectures.
The focus on events during system design opens up possibilities of persisting data in different ways. The facts generated at runtime offer a natural resource that can be easily tapped. For example, think about persistence in the model of a bookkeeping ledger, where all events are recorded. Rather than overwriting an existing entry with a new value (the CRUD model), a bookkeeper creates a new entry that represents the changed value. Microservice systems can imitate this by keeping a log of events in the order in which they come in.
An event log provides reliable auditing and simplifies debugging. When the log is provided by a messaging service, other microservices and legacy applications can subscribe to events of interest. And, in the case of failure, it is possible to replay the log at any time. This pattern is referred to as event sourcing.
Event sourcing can provide insights that are lost in traditional systems where data is overwritten. For example, on an ecommerce website, you could track which products are most often put in the cart and then removed. This information would not be available in shopping carts implemented as simple update in place persistence.
In a complex microservices system, queries often need to join data in ways not supported by the initial domain model. This is especially true when the model is event sourced. The Command Query Responsibility Segregation (CQRS) pattern separates the read and write models of a system.
CQRS decouples the microservice writing an event from readers that might be performing some action in response to the event. This increases reliability, stability, and availability of the system. The read and write sides can then be scaled independently, taking best advantage of the available resources.
“A decoupled design like this (using event sourcing and CQRS) makes for a lot of flexibility, gives us more headroom and knobs to turn, and allows us to better optimize the hardware efficiency, putting the money where it matters most.”
In under 15 minutes:
In under an hour:
Really dig in: