Modularity in the systems we are building is very important, but there are anti-modularity forces that we must deal with to be able to achieve this modularity. Increasing technical debt by taking shortcuts and splitting a system into microservices without considering the business domains are two examples of such forces. In a presentation at the recent Event-driven Microservices Conference in Amsterdam, held by AxonIQ, Allard Buijze shared his thoughts and experience building systems based on DDD, CQRS, microservices and event sourcing.
For Buijze, CTO at AxonIQ, another example of anti-modularity is noun driven design. Normally it’s used to find the objects in an application by looking for the nouns, but instead of objects we are now creating services. If you have to redeploy multiple services at once, or if a service is depending on other services before it can perform its tasks, then you have moved from a monolith to a distributed monolith with all its problems, not to a proper microservices architecture.
For Buijze, moving to microservices is a journey; you shouldn’t just go from a business model to microservices in one step. Instead, you should start with a well-structured and modular monolith and then slowly, as the need arises, separate out microservices one by one. He emphasizes that the need for creating a microservice should always come from non-functional requirements.
The most important ingredient needed in order to extract components into microservices is for Buijze location transparency. A component should neither be aware of, nor make any assumptions about, the location of a component it interacts with. This means that a component can be extracted and can become a service without the need to rewrite the communication between existing components and this new service.
Events are a common way to solve location transparency. Instead of a service directly calling the services it wants to interact with, it can publish an event, and anyone interested can listen. Buijze point out that an important aspect of using events is that we have inverted the dependency - the components or services subscribing to events are now indirectly watching the event emitting service.
A common mistake Buijze has experienced when only using events is that they are also used to indirectly request something to happen in another service. This increases the coupling between services, possibly with two-way dependencies, and this can create a very entangled choreography, like in this example where no service is responsible for the business process:
There are different reasons why components want to communicate. Besides events, we need two other types of messages:, Commands which represent an intent to change something, and Queries to fulfil a need for information. In the example above, the Order services could use commands to request payments and shipping, and the Shipping service could use a query to request order details. Buijze therefore recommends that we consider commands and queries as much as events.
Another concept related to events is event sourcing, where a component doesn't store the state of entities, but all the events that led to the state for each entity. For Buijze, event sourcing is about capturing the truth, the whole truth, and nothing but the truth. But he points out that unless you do this correctly, you cannot guarantee that you have the whole truth. A test to verify if you are doing event sourcing correctly is to throw away everything except the event store. If you after that still can reproduce the state of the application, then you are doing event sourcing. He also points out that for this to work in a service, it must itself use the events it publishes for its own state in a consistent way.
Event sourcing has some benefits, both from a business and a technical perspective. Auditing and a single source of truth are just two examples. There are challenges as well, like an increasing storage size, and the fact that it may be complex to implement, even though Buijze believes these challenges are something of the past. The real challenge is event thinking, but Buijze thinks events are a much more natural way of thinking about the behaviour of an application. He recommends that we forget about state and concentrate on behaviour. It’s much easier to understand what an application is doing when using events and commands, because they describe what the application does in a functional way. But he also recommends against event sourcing everything. It’s a tool that like other tools, should only be used where it makes sense.
Buijze concludes by emphasizing that all communication is a form of contract. One disadvantage with events is that you have a contract with lots of unknown components. You therefore should restrict the scope of the events, and the contracts. Avoid coupling across bounded contexts. Within one context all services speak the same language and can understand all events, but many events, especially the events in event sourcing, are only useful within the context and should not be published to the outside. For events that are publicly interesting, maybe they should be transformed into new events to adapt to a public API.
In a blog post from 2015, Martin Fowler wrote that he had noticed a pattern where almost all the successful microservice stories had started with a monolith. Shortly after, Stefan Tilkov wrote in a blog post that he is firmly convinced that when the goal is a microservices architecture, starting with a monolith is usually exactly the wrong thing to do.
In a presentation at DDD Europe 2019, Eric Evans described different kinds of bounded contexts, some that may be especially useful in an event-based system.
The presentations at the conference were recorded and will be publicly available during the coming weeks.