In a microservices architecture, the individual services form the core components of the software system. If the services are not designed correctly, the touted benefits of this style of architecture will fail to materialize. So it follows that the main design activity is one of identifying the individual services correctly.
From my previous posts in this series (Prelude, Introduction, Evolution), we have talked about monolithic systems that have been around for decades and how microservices evolved from them in recent years. There is an abundance of design knowledge and experience from the monolith era. So before delving into microservice design, let’s look at how things are done in a monolith application. A complex domain is almost always split into smaller parts first (either subdomains, subsystems or modules). Within a single process we use various forms of decomposition techniques to accomplish this. In order to avoid ending up with a Big Ball of Mud, two core principles are often used as guidelines for decomposition. One is ‘High Cohesion’ and the other is ‘Loose Coupling’. If you have been designing software you are probably familiar with these already and their meaning obvious. Hence I will talk about it only in brief here as a refresher for microservices.
High Cohesion and the Single Responsibility Principle
So what does cohesion mean ?
Cohesion is defined as functional relatedness of the elements of a module or the degree to which the elements inside a module belong together. (Source)
Sounds easy right ? In practice however, it is one of the hardest things to get right. Because…
Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities is much of what software design is really about.
Robert C. Martin
A well known principle defined by Robert C. Martin based on cohesion is the ‘Single Responsibility Principle‘ (SRP). This states that a software module should have one and only one reason to change. Another way of putting this is that each module should have one specific responsibility. So how is change related to responsibility ? Martin states that each responsibility is an axis of change and so if it has only one responsibility it will only need to change in response to fulfilling that one responsibility. (I should also mention an important corollary to this – An axis of change is only an axis of change if the changes actually occur. So make sure that you are in fact capturing only aspects that can change)
One way of translating this principle at the domain level is to decompose your system such that each module is responsible for just one business function. Another aspect that affects the way changes occur, in reality, is to think about the people that cause these changes. Martin in fact goes on to state that at the crux of the SRP principle are the people that originate the change. What this means for maintaining cohesion is that when a module needs to change, that change should be originated by a single tight knit group of people that represent the business functionality.
The second principle that goes hand in hand with high cohesion is loose coupling. This principle aims at minimizing dependencies between individual modules. When you have tight coupling between modules, a change in one will affect others in undesirable ways. Loose coupling mandates that when one module changes internally, that it should not affect any other module. So the interrelations between collaborating modules should be so designed that each module knows nothing of the internal details of the others. The only way they interact with other modules is through well defined external interfaces. The interaction should also be scoped to just the minimum that is required to accomplish a business function. Increasing the number of interactions will directly affect the performance of the system especially when these interactions occur outside the process boundary as in the microservices architecture.
These two principles if applied correctly to decompose a monolith into smaller parts should result in a system that at the very least is easy to develop, read, maintain and evolve.
It turns out that to identify microservices these two principles serve as our guidelines as well.
So that begs the question as to why even attempt microservices if a well designed monolith can essentially give you the same desirable qualities as that of microservices system ? Eric Evans, the author of Domain Driven Design (DDD) in his talk on DDD and microservices recounted his experience about this. He states that in the real world, monoliths have been designed for decades using this approach but that hasn’t worked in the long term. A main reason being that these design techniques do not enforce a physical boundary between modules. The lack of barriers makes it easy and convenient to break the design principles as systems grow complex and large over time.
Of course there are always exceptions to the rule but that the vast majority veer towards being a Big Ball of Mud.
So in essence we can look for aspects in our system that confirm to high cohesion in terms of business functionality and loose coupling in terms of interactions as a starting step to identifying microservices. The most often recommended approach in the microservices community, that is used in practice to achieve a design that adheres to these principles is the DDD paradigm. So in the next post I will delve into what it is and how it can aid microservices design.