In part 1 introduced the concept of microservices, what they are and the role they play. Then I’ll talked about the issues they try to solve and an advised process of breaking down an existing codebase.
In this part I will introduce Russ Mile’s life-preserver diagram and talk about the role Event Sourcing can play in a microservice architecture. I will also talk about how we want our microservices to talk to each other.
In part 3 I’ll talk about stressing our anti-fragile systems to expose weaknesses before they occur naturally and the benefits of microservices beyond architecture and deployments.
Disclaimer: I had no experience with microservices before this workshop and my current experience since has been implementing a test system comprised of microservices. I have also advised within my team at work how this type of architecture could be adopted.
If you’ve read part 1 or are familiar with the topic already, you have an idea of what a microservice is. There is, however, no definitive definition of what exactly classifies an application as a microservice.
Presenting this topic to my department led to a discussion of “What app’s of ours, if any, are microservices?”. It was generally agreed that we, as a unit, would consider some to be microservices, others on their way to becoming microservices and the rest not being a fit and likely never to be. We added the term to our ubiquitous language, content in our definition regardless of whether it clashes with someone else’s. As the microservices themselves are internal, this name serves to help us understand their responsibilities without interfering with the world outside of us.
In part 1 I also spoke about Russ Miles, whose workshop I attended and is who introduced me to the following diagram. Note that the diagram uses Event Sourcing and CQRS as a means of storing and querying data. Don’t be put off if you are not doing Event Sourcing, as the diagram and techniques it suggests are just as valid without it. When you see event, think data and when you see command and query, think database INSERTS and SELECTS respectively, or equivalent.
The life-preserver diagram splits components into two circles, the inner circle represents what is within our control as a working unit, with the outer circle containing what is affected by external factors.
The inner circle contains our components (our apps) which are entirely within our control, within our sphere of influence. They are changed at a rate specified by the team. For this to be possible they must be isolated from the outside world, which would be another factor in its rate of change otherwise.
Imagine a consumer asking: “Can you represent the data in JSON instead?” or “We’d love to have a header in all responses so we can track our requests“.
If users could make demands of these inner-circle components we wouldn’t have complete control over them. This is where the outer circle comes into use. The sphere of external influence. Here lies our components which are designed to change to according to external influence. The above questions, for example representing data as JSON, could be satisfied here without having to change the inner components.
Let’s talk about the individual components in the diagram.
The EventStore is specific to an Event Sourced system. If you’re not storing your data as events, instead storing state (which is much more common) then this is where your database will live.
Working from the centre, outwards, the first is the EventStore. In part 1 we discussed what is anti-fragility (and will discuss this more in part 3), whilst we will strive towards anti-fragility for all other components, the EventStore instead has to be robust and resilient (i.e no loss of data and potentially no down-time) as here is where we persist all of our data. In a more complex system which follows this architecture, there may be further persistent stores tied to specific components (within our sphere of influence).
The role of an Aggregate is to receive commands and to populate corresponding events into the Event Store. It knows the structure of the events in the Event Store and like the Event Store is within our sphere of influence.
An Aggregate is a microservice.
The breadth of its responsibility should come down to shared levels of availability among use cases. For example in a banking system where withdrawing money is a priority of depositing money (which may only be done once a month), then it would make sense for these two use cases to be in separate Aggregates. You could have an Aggregate for depositing with its own SLA’s which is responsible for receiving commands and writing deposit events. The withdraw Aggregate similarly writes withdraw events, again with its own separate SLA’s.
If the two use cases have the same availability requirements, it would be best to make a single Aggregate which does both deposit and withdrawals. As its job is still in receiving commands and output events it still meets our expectations of what is a microservice? from part 1.
For an Aggregate to be able to write events to the database, it may first need to be in sync with the Event Store. To achieve this the Aggregate first plays through the events related to those it will write. During this time it can be considered to be in “replay” mode and until it is caught up it would not accept commands to be written as events.
The view handles the query side of CQRS. It is within our sphere of influence and has direct access to the Event Store. The view handles our SELECT statements (or equivalent). It knows the format of the Event Store (or relational database, etc.) and returns the data which has been queried for by a third party.
A View is a microservice.
The View’s scope should come down to your service agreements. Back to our banking example, let’s say our bank is really popular among businesses in Manchester and individuals in Leeds. The businesses will be doing lots of reads whereas the people of Leeds will be requesting data comparatively less often. Splitting the responsibility of data availability makes sense here. We could have a View for Manchester we caches data for Manchester customers and a View for Leeds which goes to the Event Store when data is requested.
The people of Leeds won’t have to wait forever to view their balance despite the businesses of Manchester hammering their own View. The businesses have fast response times for the needs as their View pre-loads the data it is expecting to be queried on.
If even more businesses in Manchester start using the bank then the respective View could be replicated quite easily, with only the Gateway needing to know about the the new microservice….
I have been referencing commands and queries throughout this post, which the Aggregate and the View receive respectively. Now we come to what sends these messages. The Gateway. We have now left the cosy bubble of our sphere of influence and are out in the real world where things change around us and we have to keep up.
A Gateway is not a microservice.
Why is the Gateway not a microservice? One reason is that it knows too much. If we have a single Gateway in our sphere of external influence, then it is responsible for translating external requests into either a command or a query, depending on what the request was. It therefore has knowledge of both Aggregates and Views and the roles they play.
Gateways could also be given the job of exposing to the outside world what services are currently available. By implementing health-checks (whereby an app exposes its current ability to perform its actions at a given time), the Gateway can reject, or otherwise handle, requests to these services which are unavailable, giving faster feedback to the outside world. An app may be down for a number of reasons. In the case of an Aggregate it may be in the “replay” mode mentioned above, and View’s can be implemented to do something very similar for performance.
The diagram above shows two Gateways. This is to simplify the interactions with the Aggregate and the View pictured above. It would be perfectly valid for the Gateway to talk to both the Aggregate and the View as it is not a microservice.
Aggregates and Views are only two examples of microservices and are ideal to use when breaking down or rewriting an existing application. A microservice architecture from the ground up will likely have microservices for use cases such as logging in. This microservices for authentication may be the first point of call for a Gateway.
This leads us on to how we do want our microservices to talk to each other. The analogy Russ used was that our microservices should talk to each other as if they were executables chained by unix pipes:
microservice.exe | microservice.exe | microservice.exe
In other words our microservices will ideally have one direction of flow, taking an input from a previous microservice in the chain and passing an output to the next. This keeps our microservices decoupled and clearly outlines the role of any microservice. In the event that an app needs replacing, its contracts with its neighbours in the chain are already defined. For example a team developing a new authentication service will already know the requests it will receive, say, from a Gateway, and it will know who it is sending requests on to, for example an authorisation microservice.
We can follow this model on a larger scale. If we think of our overall sphere of influence (the larger circle in the diagram and everything within it) as a Bounded Context, ideally our Bounded Context would communicate in the same manner, a single of flow along a one-dimensional chain:
Team A's bounded context | Team B's bounded context | Team C's bounded context
In part 3 I will pick up where I left off in part 1, discussing anti-fragility. I will talk about how we can apply stressors to our system to alleviate any doubt we may have of our microservices in production. I will end these series of posts by mentioning the benefits of using microservice beyond architecture and deployments, specifically talking about a change in culture.