''Event-Driven Architecture'' Manifesto for Distributed Enterprise Applications
“The biggest outcome of the summit was recognizing that when people talk about 'Events,' they actually mean some quite different things.” Let's explore all the options.
Join the DZone community and get the full member experience.
Join For FreeIn his February 2017 blog "What Do You Mean by "Event-Driven?", Martin Fowler – one of the founding fathers of the Agile Software Manifesto – outlines the broad conclusions of a late-2016 summit that included senior developers from all over the World. The summit was held “to discuss the nature of 'event-Driven' applications." According to Fowler, the "biggest outcome of the summit was recognizing that when people talk about ‘events,' they actually mean some quite different things."
Let's clear things up.
Always Asynchronous
The first of the two eventing patterns that Martin details in his blog is "Event Notification."
In this pattern, “An event need not carry much data on it, often just some ID information and a link back to the sender that can be queried for more information," if desired. “A key element of event notification is that the source system doesn’t really care much about the response. Often it doesn’t expect any answer at all, or if there is a response that the source does care about, it’s indirect."
In other words, a fundamental principle of the "Event Notification Pattern" – and "Event-Driven Architecture," more generally – is that communication between the various software components composing a "Distributed Application" is Always-Asynchronous: “If there is a response … it’s indirect."
Whilst this was never a requirement of "Service-Oriented Architecture" (SOA) where components are "loosely-coupled," it becomes our first principle of "Event-Driven Architecture," or where components must be entirely "decoupled."
Martin immediately goes on to warn us about the use of events: “The danger is that it’s very easy to make nicely decoupled systems … but you have to be careful of the trap… A simple example of this trap is when an event used as a passive-aggressive command. This happens when the source system expects the recipient to carry out an action, and ought to use a command message to show that intention, but styles the message as an event instead."
In fact, it’s easy to spot confusion here in these words. On the inside of an individual Software Component, its internal operations will always be based upon commands. However, all communication between a (source) Software Component (versus "source system") and the other software components that make up any given Distributed Application must, by definition – in the context of ‘Event-Driven Architecture’ (EDA) – be based upon Events. If communication between software components is direct (via "Commands"), then we are clearly no longer within the realm of EDA and can no longer even imagine building the sort of “nicely decoupled systems” that Martin praised only a few sentences earlier.
Even the phrase “when the source system expects the recipient to carry out an action” seems to completely miss the point that Martin himself made only a few lines earlier: “If there is a response that the source [system] does care about, it’s indirect."
Not only does the source system have no idea from which system any response might come, it has no idea whether there will even be a response. There is no "Target" system! Given this, it is impossible to permit, within the context of EDA, that a source system “ought to use a command message."
"To whom" is the question, and by using which version of their API? Additionally, what if there are multiple (possible) targets (common in EDA) each of which is unknown and unknowable, and each of which uses a different API?
When reflecting upon the real world, it is difficult to accept as axiomatic that “when the source system expects the recipient to carry out an action … [it] ought to use a command message to show that intention.” If a client telephones a Sales Representative to request a Quote, and "SalesRep" needs to get back to them the next day with the details, in real world terms, a bona fide "Event" just took place – "Quote.Requested" – and there was never any doubt as to the intention of the source, or the expected response of "SalesRep." Above and beyond this simple scenario, it is likewise difficult to accept that a "Command: – either in software or in a restaurant – can ever represent more than a "Request," which may or may not be served.
Should such a scenario really be modeled as a "Command" (for example, "Create.Quote") given that no synchronous response is allowed, and given that the source system has no idea whatsoever if or when there will be a response. And what if several different Sales Representatives were contacted at the same time in order to secure the best price – as is common in the real world – and as a consequence, several individual responses can potentially be expected (depending on "SalesRep" availability) for the same request? Can this even be modeled as a "Command," as we are vigorously instructed to do? It should instead be modelled using the "Requested" Event Pattern.
Single-Entity-Owner
Martin named the second Event Pattern discussed in his Blog as "Event-Carried State Transfer" (because that sounds like REST). Unlike the "Event Notification Pattern," the "Event-Carried State Transfer Pattern "demands that event payloads include the details of all state-changes made to the underlying entities.
This pattern “shows up when you want to update clients of a system in such a way that they don’t need to contact the source system in order to do further work. A customer management system might fire off events whenever a customer changes their details (such as an address) with events that contain details of the data that changed. A recipient can then update its own copy of customer data with the changes, so that it never needs to talk to the main customer system." Indeed, a recipient can do whatever it chooses to do with that information; in Real-Time.
“An obvious down-side of this pattern is that there’s lots of data schlepped around and lots of copies," but we are nonetheless reassured: “What we gain is greater resilience, since the recipient systems can function if the customer system is/becomes unavailable.”
I found this last statement quite surprising given that Martin himself informed us in another (much earlier) Blog, that “this is a two-edged sword. A component may continue operating, but it will be working on out of date information if it’s not receiving events as things change.” It would appear however that Martin has since been able to perceive the problem: “Many people find the event processing adds a lot of complexity to an application (although I do wonder if that’s more due to poor separation between components that derive a working copy and components that do the domain processing).”
Bingo!
This brings us to what must become the second fundamental principle of Event-Driven Architecture for Distributed Enterprise Applications: each "Entity" must have a single "Owner-Component." It doesn’t matter how many other software components replicate the nominated entity (e.g. SalesOrder), but there should only ever be one component that owns it; only one that can ever update specific entities and "Publish" the corresponding events. All other software components making up the Distributed Application should have no right to do anything other than "Subscribe" to events concerning the relevant "Entity Type."
Such a design will obviously be referred to by purists as "Eventual Consistency," as subscribers will only "eventually" come to be consistent with updated data held on the Owner-Component. However, given that the delay for such updates to reach Subscriber-components should be measurable in milliseconds, this latency will in reality be very similar to what is witnessed in synchronous operations: it will be identical from the human perspective.
Needless to say, the replicated records will never be locked on the shadow databases (where no updates are to be performed directly on that Entity). As such, they will always be updated without delay or complication. The performance of such a topology will even be sufficient to push the same "State-Changed" events simultaneously to hundreds of distinct Frontend applications, if built using the latest ‘Model-View-Broker’ (MVB) Pattern.
And what if the Owner-Component is missing some necessary fields in its data model, with regards to a particular Entity Type (like "Customer")?
The answer seems quite apparent:
- You add those fields to the Owner-Component’s model (and have a single user group maintain the entities for which they are nominated), or
- You build a spaghetti system that no one can understand, support or even diagram.
Where some of an Entity’s fields are the responsibility of Component A, others the responsibility of Component B, and others still the responsibility of Component C. Then, you build in some concurrency protection to ensure that if, for example, an Entity was changed in Component A whilst simultaneously changed in Component B, and if the changed field is common to both (e.g. address), then you roll-back one of the two components depending on who arrived first. This informs the user with a notification, but if the roll-back fails.
It is also worth pointing out that only option 1 noted above is fully asynchronous, requiring no use whatsoever of Commands. It is the only option available in a true EDA. Likewise, if the Owner-Component goes down in this scenario, the other components can never possibly “be working on out of date information." If the owner is down, no updates can ever be made to the entities that it owns, and any updates that were made will have already triggered events (that will have already resulted in updates to the Entity Stores of subscribed components).
Such a Single-Entity-Owner rule, whilst clearly related, should not be confused with the "Bounded Contexts" first described in Eric Evans’ 2003 Book, Domain-Driven Design.
Evans suggested the use of "Bounded Contexts" to group together all entity types that form part of a given business domain (e.g. the "Customer," "Product" and "SalesOrder" Entity Types that are each part of the "Sales" domain).
However, he lays no interdiction to having the same business Entity, such as "Product." managed partially by distinct systems. Whilst he does propose a mapping between different forms of the same Entity Type in cases where it is shared across domains, there is nothing declaring that each entity should be owned, in its entirety, by a single Software Component.
Robust distributed enterprise applications will in fact often demand the opposite of what Evans suggests: they will demand that all attributes/fields of an Entity that is shared across different business domains, are all added to the "domain model" of that Software Component that is nominated as an Entity’s "Owner."
Given there should (typically) be a strong link between any particular "Business Domain" and its underlying "Software Component" host (like Salesforce), and given that the same teams will likely be charged with supporting both, not having a Single-Entity-Owner rule results in system complexities that produce significantly more hardship than the inconvenience of declaring that the domain model of a particular Owner-Component shall serve as the master for the entire Distributed Application, for specific entities.
Far more recently, Evans declared that "generic off-the-shelf business contexts" can make domain boundaries far easier to draw, given that they are proprietary and therefore imposed to a large extent.
In the case of distributed enterprise applications built upon (at least some) proprietary software components, this overlooks a fairly obvious issue: there will often be several proprietary software components touching upon exactly the same business domain (e.g. Salesforce and SAP), in which case only one of them should be chosen as the owner of each relevant entity. In modern proprietary solutions, it is relatively straightforward to add custom fields even to their "proprietary" models, a capability that ought to influence the decision on the principal Owner-Component in each business domain.
Macroservices
The "Single-Entity-Owner" principle, along with the first EDA principle of "Always-Asynchronous," gives rise to a new concept in software development, and also to our third EDA principle: "Macroservices."
Microservices don’t care how many distinct components/systems are involved in any given operation, meaning a very certain dependence on synchronous communication. If one Microservice call fails, certain others in the chain need to know about it A.S.A.P.
There will typically be a lead Microservice that is called synchronously, which depends upon the results of all the other microservices that it itself called (synchronously or asynchronously). Such a Microservice scenario will often depend upon synchronous communication between distinct software components: outlawed in EDA.
Macroservices (conversely) should only ever execute on a single Software Component, and should always be built at Entity Type-level (e.g. "SalesOrder"). Hence "Macro." In order to be coherent with the overall architecture of a Distributed Application, each Entity’s State-changes can be published by its Owner-Component (within microseconds of the event).
Entity Query Store
Returning to our first EDA principle of "Always-Asynchronous" communication between software components. The only possible exception that might be permitted concerns "Lightweight Components."
Such lightweight software components may neither have their own database nor an adequate means of persistence. In other words, they can never be fully autonomous, as should typically be expected of any "worthwhile" software components in a Distributed Enterprise Application. The first question that should always be asked in such cases is whether these lightweight components ought to be merged with other more heavyweight components, in order to obtain the needed persistence.
If this is not feasible, one possible exception to Always-Asynchronous communication can be imagined: an "Entity Query Store" could be built at the hub of the Event-Driven Architecture. Conceptually, alongside the Event Broker (several of which happen to provide precisely such persistence out-of-the-box). This "Entity Query Store" (EQS) would subscribe to all the events of all the Entity Types that it replicates so it could maintain an always-up-to-date image of each and every Entity Type hosted.
As per the "Event Sourcing Pattern," received events would be immediately "folded" (i.e. merged) with the current state to build the latest "Folded State" of each Entity. What we typically refer to today as "Data."
Such an EQS could be queried – synchronously if necessary – by any software component that wished, as per the "CQRS Pattern." Nonetheless, this Synchronous-EQS approach should be used only if the "Requested Event Pattern" cannot be employed to support Asynchronous reads, as this Pattern provides the ability to entirely abstract-away the data source. Maybe the source is a database today, but maybe it will be an In-Memory EQS in the future.
The reason I referred to such an EQS as only a “possible exception” to "Always-Asynchronous" communication is because an EQS is not in fact a "Software Component": it contains no (Distributed) Application logic, is completely technology-agnostic, and provides nothing at all beyond a generic – read-only – shared persistence layer for the entire Distributed Application (that can potentially be queried synchronously). Such an EQS, whilst enabling synchronous internal queries, does not break any fundamental tenet of Event-Driven Architecture.
For that reason, I shall nominate the "Entity Query Store" as the fourth principle of EDA.
"Event-Driven Architecture" Principles for Distributed Enterprise Applications
In concluding his blog, Martin wrote: “I’d love to write some definitive treatise that sorts all this confusion out, and gives solid guidelines on how to do each pattern well, and when it should be used. Sadly, I don’t have the time to do it." Given this, it seems it's time to get the ball rolling:
- "Always-Asynchronous" communication between Software Components
- Each "Entity" should be owned by a single "Owner-Component"
- Provision of single-software-component, Entity Type-level "Macroservices" for the exposure of asynchronous operations to other software components of the Distributed Application
- Use of an "Entity Query Store" as a read-only (and ideally In-Memory) shared persistence layer
(To which we can add) - Do not use "Event Notification" as State changes made to an Entity will not be immediately available in the Payload, which will consequently require direct, synchronous communication between concerned software components (at which time the source might be down). It likewise does not permit efficient replication on an "Entity Query Store," or on any other interested components
- Don’t let "Publisher" software components perform API calls/send Commands to other software components. Neither of those things are "Publishing," and neither are Event-Driven
NB: more detailed technical explanation of each EDA Principle can be found in the companion: "Event-Oriented Architecture" Manifesto
Published at DZone with permission of Cameron HUNT. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments