In this article I’d like to discuss from a birds eye view, how business processes, distributed across multiple microservices can be simplified using an abstraction model. This is part of a series of articles about how microservice architecture can be applied in a domain centric way without constantly dealing with technical aspects.
If you haven’t done it yet, I recommend checking my other articles in this series.
First of all let me clarify what I mean by process in software design. A process in my interpretation is a series of small tasks a software program can execute, in a specific order given by an algorithm. It has one entry point which initiates the execution and one or more outputs, communicating the result of the process outside of it.
Running and terminating a process is relatively straightforward, from the programmer point of view it’s basically implementing an algorithm which is modeling the business domain within the limits of the chosen framework and programming language.
The most interesting part is initiating the process which can be done in many ways, especially in a distributed ecosystem such as microservices. So let’s focus on how triggering a process can be generalized in a technology agnostic way, but still, giving the possibility to model a large variety of scenarios such as Http requests, ServiceBus messages, time triggered jobs or file uploads.
I think the most suitable candidate for the job is Event Driven Architecture, which when applied means that every interaction from inside and outside of the system is modeled by starting with an event and executing the domain process in the form of one or multiple event handlers. I’m not the one inventing this, there are many excellent articles about the benefits of combining event-driven and microservices architectures together.
So lets see a flow of creating an order and updating the inventory in a microservice architecture where an order and an inventory service are involved.
Because we talk about Event Driven Architecture, we should clarify a bit what an event is.
The Event
In its definition, the event contains the domain model encapsulating all the input (output of the publisher) parameters for the processes that it triggers. One or many flows can be triggered by an event depending on how many event handlers are. This means that the event handler is always the entry point of a flow.
The next example presents the handling of an event, published by another microservice:
Subscribing to the event is done by a generic event subscriber responsible to materialize the event and resolving the event handlers. This offers a great opportunity to set contextual information (creator user, tenant, language etc.) sent by the event publisher. For more details see my article about inter service communication.
What we already have is providing the ground to implement business processes that are triggered from the inside of the microservice ecosystem, but software solutions are rarely closed systems, they have to also handle external triggers (e.g. browser requests) which have to be authorized and validated before they are executed. For this purpose we used two special type of event: Commands and Queries.
The Command
Besides holding a domain model, commands also contain in their definition an authorization resource in the form of a string which is understood by the authorization service. They always have only one command handler and one or more command validators.
An example of a command and command handler for creating an order and publishing the OrderRegistered event:
And the implementation of a command validator:
The Query
A query is basically a simpler command used to model flows that doesn’t involve a state change in the system (see Http Get ) therefore they don’t need any validation and their query handler returns domain objects as the query result.
The orchestration of authorization, validation (command only) and execution of the handler is done by the command dispatcher, following the principles of the Command design pattern. Do not confuse this with Command Query Responsibility Segregation
And a look of how all the MVC controllers should look like when handling Http requests with the command pattern:
The above can even be eliminated by customizing the request pipeline to direct requests to the command dispatcher directly, eliminating repetitive code, but please not that might also require a custom Swagger open api schema generation.
So why this trouble with commands and events and handlers, instead of implementing the domain logic directly in the controller?
To understand the benefit of this abstraction we have to put into perspective what we actually have.
We have the event subscriber and the command dispatcher which wraps every piece of logic that we will implement in the future, and this allows us to implement cross cutting concerns like exception handling, logging, data validation, authorization and custom DI and apply them in one place.
We also now have a way of extending the system with functional flows by creating events/commands/queries and handlers, focusing on business requirements without further dealing with how the flow was triggered or the existence of cross cutting concerns.
I hope with the above examples I managed to demonstrate how, using abstraction of the process triggers and execution made it possible to implement new functional features with no technical interference, focusing on domain requirements without dealing with the technical aspects we already incorporated.