No one has achieved success with microservices by just talking about them. Unfortunately, many organisations spend so much time on exactly that, debating how to approach microservices. It is as though there is one perfect approach to designing and working with microservices that only needs to be uncovered. In actual fact, there is no such definitive solution, and even if there were, it would only hold true for a little while, until changes in the organization, the business objectives, the technology frameworks and regulations make an adjusted approach necessary.
It is tempting — just as it was a decade ago with SOA Web Services — to spend a lot of time and energy on identifying the microservices. Creating an exhaustive overview of all microservices, with the exact scope and interface of each microservice well defined, is not feasible and is not a smart investment of time. It would be a lot of work, and would never be complete. The definition of microservices is not an end in itself and giving in to this temptation is a serious risk. Microservices are an instrument to achieve sustained business agility in a changing world of functional and non-functional requirements and evolving technical, political, economical, and legal parameters. Microservices cannot be defined once and for all, and they should not have to be. As architects and developers we are agile and flexible. We embrace change in all aspects of our IT organizations.
Another risk organizations run that sounds familiar from the SOA era: start by focusing exclusively on the technology for implementing microservices and on the microservices platform, the underlying platform for eventually running the microservices (that do not even exist today and for which no requirements are yet known). It is all too easy to spend an unjustified amount of time on this seemingly useful exercise and after months of investigation and selection and architecting probably end up with an impractical, oversized and over-engineered platform – and no running microservices. Such discussions slow down the process of microservice adoption and obstruct the view on the essential challenges, setting up an organization for disappointing results if not outright frustration.
A third category of risk can be identified: just start building microservices without having a clear need for or objective with a microservice architecture from a business perspective or – worse even– without really understanding what a microservices architecture entails from an organizational perspective. The operative keyword that they are overlooking: DevOps.
This article provides some insights and guidelines that hopefully will be useful to help propel teams of architects beyond discussions and into action. Perhaps it can also help to establish some architecture guidelines. Such as the importance of domain design.
When discussing microservices it is imperative to keep our eyes on the prize. To constantly remember what our objectives are. Microservices are not the objective – they are merely the means. Microservices are here to help us with those objectives and if they do not do so, we do not need or even want them.
Both in Development and in Operations we want to have better control and have more insight so we can change functional and non-functional aspects as dictated by business needs. And we have the impression that microservice concepts and considerations will help us get there.
A reasonable enough question to ask, apparently a tough one to answer. It seems easier to describe what the microservices architecture is intended for than to explicitly define a microservice itself. Perhaps we tend to stay away from a concrete definition because then we may find we are not so much in agreement with our peers as we thought we were? Let me try to describe what picture I see in my mind when talking about microservices:
A microservice is an independent business component that we can change, release, scale, relocate, make available, replace, charge for, report on, decide about by itself, without dependencies on other microservices (and their associated teams, owners, users etc.) A microservice has clear business value and implements a specific business capability. A microservice is owned through its entire (DevOps/BusOps) life cycle by a single team – of no more than 6-8 people. One team could own a few microservices; one service will fall under precisely one team.
The word micro-service causes confusion – leading many people to assume microservices are tiny – much smaller than ‘regular’ services. To me, one microservice can easily comprise several of our typical SOA (Web) Services. In my mind:
Most organizations will have dozens of microservices, perhaps a few hundred in cases of extreme size or functional complexity; I do not envision thousands or more microservices in an organization
Microservices can [partly] be implemented through one or more serverless functions.
Microservices can have a [part of a] SaaS service or standard application at their core.
Microservices will have to collaborate – by responding to each other’s events or participating in workflows and transactions; however, ideally they are not aware of each (only of events and/or generic platform components)
Note: There is no clear connection for me between microservices and containers. Of course, containers are a great vehicle for achieving standardized and automated CI/CD, release, scaling and management and allow us to package application and platform components together into stand alone units and as such can be a great help for implementing microservices. At the same time, one microservice can easily be implemented using several containers or totally without using containers.
Microservices are designed from business functionality and responsibility (maximize cohesion vs maximize decoupling), based on business domain decomposition. Microservices translate to technical stuff – such as deployment artifacts and containers and perhaps database schemas or partitions – and even more to organizational aspects such as ownership, planning and budgeting, coordination and team management.
Microservices can be compared with departments in a company. Relatively separate units with a clear responsibility that can be largely “encapsulated” and are interacted with in largely predefined ways that should not be by-passed by outsiders.
The size and number of departments and their scope is not easily defined in a generic manner. Some or small, some large. Some may consist of just a part timer – for example our legal department consist of a specialist hired for 6 hours per week – and others can be fairly large – the pool of truck drivers for example. As the company grows or diversifies, the departments can be split into more specialized units.
At departmental level or per department, it is decided how activities are performed, who is hired or fired, where activities are performed and when the department is available to perform any work. Too many tiny departments can introduce a lot of overhead and very complex process chains – even though they may allow a high degree of specialization and flexibility. Too large departments are hard to manage and make adopt different ways of working. Creating two departments out of a single one or combining several departments into one unit is a fairly common practice – although it may not always be a very smooth transition.
The exploration of the bounded contexts that are the breeding ground for microservices takes place in a similar way as the above search for the optimal departmental structure- as part of domain-driven design. CitingGérald Croës: “Bounded contexts are theSingle Responsibility Principle applied to your domain model. Each part of your system has its intelligence, data, and vocabulary. Each part is independent of one another.”
When defining microservices, some general guidelines are applicable to override all other, more subjective considerations:
● a microservice can be fully taken care of by a single team
● the [business] ownership of the microservice is clear – a single decision making unit regarding both functionality and operational, non-functional requirements
● a microservice is stand-alone, independent of other microservices, both logically (design time dependencies) and physically
● the implementation of the microservice is encapsulated – only known to the team
● the technology used to implement the microservice is at the discretion of the team – within certain limits: the microservice must be run on the generic enterprise infrastructure and at enterprise level – in order to reduce the list of skills the organization needs to maintain and facilitate collaboration between teams and movement of employees across team – a portfolio of allowed technologies can be defined from which teams have to choose. However, restrictions with respect to the implementation technology should be limited as these may have a negative impact on the team’s motivation and sense of ownership, the development of the microservice as well as its runtime stability and future maintainability.
● the implementation of a microservice is simple and straightforward – easy to change, easy to understand for existing and new team members and not require too many skills to acquire in order to collaborate in the team (so the technology stack should be limited)
● the microservice should provide clear business value and deliver well defined business functionality (if we cannot explain to non-IT staff what a microservice is for, we should reconsider its very existence)
● a microservice exists within a single bounded context
● a microservice can be owner of specific business data and also make use of data it is not the owner of. Data owned by the microservice can only be queried or manipulated through the microservice. Data in the bounded context of which the microservice is not the owner is read-only to the microservice; a local copy can be synchronized from the owning microservice
● a single, unified enterprise data model and a single, one-stop database with all enterprise data is not desirable and is in contrast with microservice principles. Eventual consistence across microservices [and across the data stores owned by microservices] is the common situation. ACID [cross bounded context transaction with immediate consistency] is implemented only when it is truly required and well-motivated from business requirements. Data duplication is not considered a bad thing – although the ownership of data (the single point of truth) should always be clear. Note: even within a bounded context or microservice it might be wise to explicitly mark transactions that require ACID processing, even if for the moment such processing may seem free because a relational database is used for persistency.
As Sam Newman states: “When it comes to how small is small enough, I like to think in these terms: the smaller the service, the more you maximize the benefits and downsides of microservice architecture. As you get smaller, the benefits around interdependence increase. But so too does some of the complexity that emerges from having more and more moving parts. As you get better at handling this complexity, you can strive for smaller and smaller services.” (in What are Microservices)
Two application sections that are currently part of the same microservice (or monolith) are perhaps better off in distinct microservices if they have stark differences in one or more characteristics – to such an extent that it hampers us or cramps our style. Some measures and mechanisms are expensive – so they should not be wasted on application areas that do not require them. Making functionality Highly Available or Ultra Secure or Supremely Well Tested comes at a price. Identifying areas that need special measures and isolating them as separate components (aka microservices) to ensure the elevated levels are focused and applied only where needed is one of the considerations.
The motivation for breaking off a chunk from an existing application component should be the fact that the chunk is currently not [in] a separate microservice makes it harder or more expensive to do things we want to do to only one of them, such as make functional changes, improve availability, test for regression, set up monitoring, train developers, achieve high level of confidentiality. Therefore, we branch off one or more microservices – to remove these limitations that we experience. The effort of branching off and the overhead introduced as a result of having two or more instead of a single microservice is justified by the gains from the independence we realize between the microservices. If not, we do not do it.
Characteristics that drive the decision to extract one or more microservices from an existing component [when various areas within a component hugely differ in them]:
● [lack of functional] cohesion – a microservice should concern itself with a single responsibility (again theSingle Responsibility Principle); if a microservice is found to be doing multiple, unrelated things, then it should be split up
● rate of change – if two areas have a very different change rate (one is stable, almost static and the other is constantly on the move) they probably should be taken apart; for one, the testing and CD effort will be unnecessarily large if we constantly process a subdomain that does not change
● dependencies – if two areas have no mutual dependencies – why should they be in the same microservice? if they have very different external dependencies – these dependencies may have too broad an impact (impacting a subdomain that has no need for them)
● business value & criticality level (a specialization of the previous bullet) – if one area in a component is critical to the daily operation of a business, and another area is much more mundane, they will have to be treated in very different ways – certainly in terms of Operations ; that may be a reason for identifying two different microservices from them
● usages and user communities, business domain, terminology – when the usage patterns or simply the group of users for two subdomains are very different from each other, or very different domain knowledge is required, it may well be that two teams with different cultures, communication styles and functional & technical attitudes are required; different teams implies different microservices
● ownership and decision process – if the way in which the evolution and priorities for two areas is decided is very different, it may be a problem for at least on of the if their lifecycle is intimately linked in a single microservice; time to say goodbye?
● budget and funding – similar and related to the previous characteristic : if the way the Dev and Ops of two areas is funded is very different this may be prohibitive to the evolution of one or both
● size and complexity – one team (around 8 people) is not enough to take proper care of the current component or: one specific area in a component is very complex and requires highly specialized training and skills to work on; these are reasons for considering multiple teams and [therefore] multiple microservices
● CD process requirements including QA demands – if part of a component requires a very stringent QA process – various levels of testing and approval for example and an extreme degree of automated regression testing – while a different part can be extremely relaxed, the evolution of the second part can be unnecessarily slowed down and made more complex and expensive until it is split off
● technology and skillset – when two areas require very different technologies and skillset to be developed and operated, this may put a too large burden on a single team that is resolved by having two teams (or by standardizing on a common, more narrow stack)
● security level – if one area requires very advanced confidentiality and rather extreme security measures while a different area does not, the cost – not just financially, but also in complexity of changing, testing and managing – of having specialized resources for the entire component instead of for only a subdomain may be prohibitive and splitting the special-needs microservice from the component may be in order
● massive interaction and/or shared transaction scope – if two areas have frequent interactions or they perform activities that are logically and perhaps physically part of the same transaction, that may be a reason for not breaking them up. As was stated before, interaction across microservice boundaries are more complex and certainly introduce runtime overhead; logical transactions across microservice boundaries even further increase the complexity and overhead.
● billing – usage for different areas in a component is charged for in very different ways – could be based on different metrics, different types of subscriptions, at very different price levels, to very different users or departments
● sizing, scale and [dynamic] scalability requirements – if we have to significantly scale a component because one area within that component has severe scalability requirements, we are allocating resources to scaling other areas that have far more relaxed scalability needs; that become very wasteful – and a driver for breaking the different areas apart
● availability requirements – similar to the previous discussion on scale: if one area has very advanced H/A requirements and another does not, the effort on implementing H/A for the entire component – through compute resources and people – will partly be redundant
How to Deal with Change
There is no perfect microservices design and certainly not one that will stay a perfect fit forever. Organizations are in constant flux as is the world around them. What was a great fit yesterday in terms of microservices may no longer match the situation of tomorrow. We might as well face that we do not design for posterity and instead embrace the changes and refactor our microservices accordingly.
The Agile way of working and thinking mandates us to embrace change. Change with regard to functionality and priorities and also with respect to technology, process and architecture. This mentality needs to be spread throughout the complete organization from the C-Level down to the clerks in the business departments. This is a critical aspect, which today is not often seen in organizations
We should be prepared to modify our microservices design when the situation changes – and be happy for the chance to improve instead of chagrined that our design apparently was not good enough. This can happen for example when the decision is made to implement a SaaS service, to out source part of the business activities, to implement specific security considerations regarding certain types of data or to merge the organization and its IT assets with another company. Or simply when the organization is hugely successful and is rapidly growing.
We should be prepared almost at any time to extract newly identified microservices from an existing application component. When the cracks revealing a candidate microservice begin to show and a certain subset of an existing application component or microservice is found to meaningfully differ from the larger context it lives in – for example on one or more of the characteristics listed above – we have to split off that subset as a new microservice.
This will happen so regularly, that organizations should not have any difficulty in going through this process. Given the fact that we will start out with one or very few microservices, it is very likely that we will have to split off microservices regularly. We can facilitate that process by preparing for it: Just as in the picture above, we should define and visualize the boundaries between subdomains so there is awareness in our team about the areas that – though part of a single entity – should be treated as at least a little separate from each other. We should assign names to these subdomains and use those names in user stories, documentation and in the naming conventions for our software artefacts. Interactions within the microservices and across the border between the subdomains should be created in a considered manner: whenever possible through the public channel (API or better yet – events) or at least all through a single interface. No direct references or dependencies between data models in the subdomains.
The biggest challenge with splitting microservices is data. It’s easier to split off code than it is to split data – both the data model and the actual data records. Once data is split inevitably the way transactions are supported also have to be reconsidered, at least if transactions were supported across the boundary where we now have two microservices. The transaction design perhaps should be changed or patterns like SAGA and Event Sourcing have to be considered.
Note: The reverse operation of merging two microservices into a single component should be much easier. Formerly heavily decoupled components with disjunct ownership, backlogs and budgets are combined into a single unit with permission – but no mandate – to interact more intimately. In reality, merging mature microservices does not happen very often. Only when there is no justification for having two separate microservices and the short term effort to merge is less than the long term overhead of continuing with the existing situation will a merge be considered. Unfortunately, the risk and effort are typically not justified since there is nothing immediately apparent gained from merging. By having and practicing a clear procedure for merging microservices – just as we have for splitting them – we get closer to embracing change and can better the fit of our microservices with our organization.
A simple example: a small organization runs a webshop. Its application landscape has been designed based on the functionality required, the SaaS services subscribed to, the structure of the organization and the user groups. The size of the outfit is also a factor: the IT department is currently only very small and the scale of the operation is also very limited.
However, it is clear from the beginning that the Logistics microservice comprises areas of functionality that are related but distinct as well. Warehousing – keeping track of the stock and ensuring that replenishment is done from vendors – and Shipping – picking orders from the warehouse and transfering them to shipping partners to have them delivered to the customers – are both part of Logistics. They have touchpoints and definitely deal with similar aspects of the business. Yet they are also different.
It is too early for this organization to break up the Logistics domain into Warehousing and Shipping (and more?) – but it makes sense to recognize the separate areas that may well evolve into distinct microservices. We do this by associating user stories, documentation, software artefacts, etc explicitly with their respective sub-domains – in naming and organizing them. Additionally, we try to prevent any direct dependencies between artefacts from these two subdomains. The data models should be kept separate. Interactions between the two subdomains should be explicitly managed, documented and implemented – ideally through external APIs and events.
In the same organization, a similar example are the Orders and Shopping Cart. Right now they not only share one bounded context but they are also implemented in a single service. However, chances are that Ordering and Shopping Cart will evolve at separate pace and change for different reasons. So they may eventually become separate services – and perhaps distinct bounded contexts. In doing so, we will have then to also consider the split in data and define events so the two pieces of functionality can still interoperate -even though now they’re fully decoupled.
Fewer microservices is better
We do not want many microservices. The fewer microservices we need (to achieve our true objective) the better it is. More microservices means more overhead and costs – organizational in terms of responsibilities and work coordination, in terms of complexity and administration effort (cross-domain transactions, more moving parts to oversee, monitor) and more resource usage (each microservice requires additional infrastructure resources) as well as performance (interactions – highly decoupled – across microservices will take considerably longer in terms of network latency than internal calls within a microservice).
If one microservice can bring us all we need – that would be great! One component that does it all – is that therefore a monolith? Well, literally it is. But it should not have the challenges commonly associated with monoliths (such as hard to scale, hard to change) – or it would be broken up into multiple microservices.
In reality in most situations our application landscape will be heterogeneous along various axes and too big to be taken care of by a single team. Because of relevant differences between various areas in our application landscape will be entertain the thought of splitting off some areas as microservices. Ultimately, it is the functional design – translating to bounded contexts – that probably is the biggest deciding factor in designing the microservices and therefore the eventual granularity.
Note: an interesting discussion can be held around the distinction we can make between bounded context (an area of shared, consistent business terminology and data model language along with all persistent data stores, aligned with a business sub domain) and microservice. We could argue that several microservices – with for example different scalability requirements – can be created within one bounded context. In terms of business responsibility and DevOps ownership ideally the bounded context is not split up and if there are in fact multiple microservices for pragmatic reasons, they should all be owned by the same team.
Suggestions, contributions and general feedback from the following people have made this a better article – and I thank them for it: Jeroen Kooij, Luis Weir, Sven Bernhardt, Michiel van der Sluis, Sander Rosenhart.
What are Microservices – Sam Newman – (O’Reilly Media, Inc., 2016, ISBN: 9781491966129)
Sub-domains and Bounded Contexts in Domain-Driven Design (DDD) – Lev Gorodinski , April 2013 –http://gorodinski.com/blog/2013/04/29/sub-domains-and-bounded-contexts-in-domain-driven-design-ddd/
How big is a microservice – Ben Morris, March 2015 –http://www.ben-morris.com/how-big-is-a-microservice/
Thoughts on the macro architecture and the infrastructure for microservices -Michael Douglas, April 2018 –https://medium.freecodecamp.org/microservices-from-idea-to-starting-line-ae5317a6ff02