- 1286 words
- 7 min
Notes Taken: 2021-01-28
Martin explains there are several different understandings of what Event-Driven Architecture is. All of these are valid but are distinct in their actual form. Martin delves into four of these.
At a high level, there is a difference between events and commands. Events explain, 'something has happened', the receiver is free to ignore the event or do something in response to the event occurring. Commands explain, 'something should happen', the producer of the command has to know what command to issue. Events and commands are both valid, the disadvantage of commands is that the producer has to know which system wants to react to a command, and the producer must issue the appropriate command - arguably this creates more coupling.
The four patterns of Event-Driven Architecture:
- Event Notification
- Event Carried State Transfer
- Event Sourcing
- CQRS (Command Query Responsibility Segregation)
Pattern 1: Event Notification
Martin pictures a system where a user changes their address in the Customer Management System, this should cause the Insurance Quoting System to calculate a new quote for the customer. In a classic model, the customer management system must call out to the Insurance Quoting System to order a requote. This creates a strong coupling, in particular the Customer Management System is dependent on the Insurance Quoting System. This is likely suboptimal as the Insurance Quoting System is a more specialised and less general system than the Customer Management System; Martin argues dependencies should usually be drawn from specialised systems to more general systems. Instead of calling from the Customer Management System to the Insurance Quoting System, the Customer Management System could publish an event 'CustomerAddressChange' to a queue, and the Insurance Quoting System could listen for these events and requote the customer when it receives the event. This effectively alters a coupling of 'Customer Management System -> Insurance Quoting System' into Customer Management System -> Event Queue <- Insurance Quoting System. This is the most basic of event architectures, it simply alters the way dependencies can be drawn.
Pattern 2: Event Carried State Transfer
A less common pattern. Built atop Event Notification, when we receive an event, we pull the data from the Customer Management System and store the relevant parts in our own database. This means the Insurance Quoting System can stay available, even when the Customer Management System goes down. The problem is we now have to copies of data so we lose consistency.
Pattern 3: Event Sourcing
The previous two patterns are largely still CRUD, when we make a change, we save it to a database, overwriting the previous value and maybe publishing an event. Event Sourcing changes this, we both overwrite the current state but we also log an event to an event log. We now have a system with two forms of state, the application state and the event log. The application state is the point in time state, which can be recovered from the event log. The standard for an event sourcing system, is that at any point in time the application state can be destroyed and we can reliably recover from the event log. In Event Sourcing, we move from being oriented around application state, with events as an optional extra, to being oriented around a log of all events, with application state derived from that log.
Developers are constantly using an example of an event sourcing program, almost all vcs systems such as git are event sourced. The current commit/working state is the application state, and branches, commits, etc are all just events in the event log. For business people, an accounting system makes for a good analogy to an event sourced system. The current balance of accounts is the application state, but the series of entries in the ledger is the set of events that are used to construct that state.
One great benefit of Event Sourcing is that we can change the models of application state at any time, and fully reconstruct from our event log. Instead of writing database migrations when data models change, like we do in the SQL world, we just change how state is derived from events. We can even test systems in a read only mode against production, by reading the event log to construct a new application state. One key advantage Martin mentions is that Event Sourcing enables a 'Memory Image'. This essentially means that our application state can be completely ephemeral, and in memory; you don't need a database for application state, you only need to read from the event log and construct the application state in memory. This can be very fast and performant. The other benefit of this is that memory is super fast, and you can essentially avoid database calls, whilst having persistence. Martin mentions, think about the opportunities opening up here as we begin to see more and more systems with high amounts of RAM or super fast non-volatile RAM.
There are added complexities. Consider how you can order events reliably. Consider how you ensure transactional guarantees, if you write to the event log, you must also update the application state at the same time. You can't update application state before writing to the event log because then you have a working state that is ahead of the event log state, and remember, the event log state is the source of truth. Event Sourcing systems generally bring asynchrony with them, and this can add quite a bit of complexity. You must also consider consistency, what does it mean to write to the event log, if other systems are going to react to those events, they will not be consistent at the point of time you write to the event log. This is a trade off with all event driven architectures however.
Martin mentions in passing that he is very surprised by how uncommon Event Sourcing is in practice.
Martin brings forward a final and very important consideration when Event Sourcing. We must carefully consider which events are worth storing, and which are not. Suppose there is a service that receives an order to 'buy 15 widgets', the service then creates a transaction that is stored in the application state, this transaction contains additional info like the tax applied and any discounts applied. The service could persist an event with the business logic derived details, such as tax and discounts, or an event that most closely mirrors the input request. The advantage of the former is that the event log contains a more precise record of what was computed at the time the request was received, the advantage with the latter is that it contains only the bare minimum required to recompute the application state, and nothing more. In practice, many systems persist both types of events. Greg Young says that there should be no business logic between your events and your application state.
Pattern 4: Command Query Responsibility Segregation
Martin warns outright, this is a complex pattern that is more often than not implemented incorrectly, and can cause troubles if so. The idea here is to separate out the write (command) path from the read (query) path of your system. This is particularly useful when you have an application that is read from much more frequently than it is read from, especially if the reads are of a form derived from the write form.