David deBoisblanc, April 13, 2021
“What is the purpose of this particular microservice ?“
If the answer contains the word “and” maybe you need to reexamine your design.
The result of “and” is a microservice that has more than one functional purpose. As you scale and introduce new business requirements, the application becomes increasingly difficult to maintain. Perhaps more egregious, the communication pattern has an additional multiplier that can lead to ‘circuit breaker’ hyperactivity. Also, it increases complexity in isolating issues for resolution and remediation.
Combining purposes in a particular microservice can appear to be a more expedient way to get new functionality implemented. Hence, the temptation to do it. The usual result, however, is an overall extension of the timeline as challenges arise.
Single Responsibility Principle
What we are talking about is the “S” in SOLID design principles as first laid out by Robert C. Martin. The Single Responsibility Principle is the first tenet of Solid:
- Single Responsibility Principle
- Open/Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion
It was originally defined as:
“A class should have one, and only one, reason to change.”
-Robert C. Martin
Many experienced developers already strive to practice this in their development and know the idea even if they have never studied SOLID design. At the coding level it drives plenty of refactoring into objects or functional design by developers with pride in their code. The principle is true for classes, components and microservices.
Single Responsibility Principle in Microservices
Design
Robert Fowler has laid out 9 principles for microservices that we strongly agree to and have applied in our deployments. Here is my version based on those:
- Componentization via services organized around business capabilities and cross cutting services.
- Smart Endpoints and Dumb Pipes
- Decentralized Governance and Data Management
- Infrastructure Automation
- Design for Failure
The Single Responsibility Principle is encapsulated in the first of these principles, 1) Componentization, and is an enabler of the 5th principle, 5) Design for Failure.
Often the technology stack is selected first and often driven by the current inventory of skills and licenses. As a result, there is plenty of focus on that selection process with some notional understanding of the application architecture. However the need to put strong focus on the following two steps is a greater use of energy towards a successful product.
- Mapping the components: Utilizing the Single Responsibility Principle, the services and their respective boundaries are defined.
- Communication Pattern is Designed.
These two steps are critical for a successful and super performing product and are very difficult to reverse when inadequately designed. As an extra benefit, these steps can lead to a modification of the technology stack, especially since the technology stacks can be independent from each other.
Changing or new requirements is a fact of life for developers and architects. In the Agile methodology this fact is embraced and embedded in the process, yet this does not reduce the challenges in design and development of systems. In fact, it amplifies the need for proper design and proper coding execution of those designs.
Changes not only to business functions but changes to operational code (eg, new security settings, etc.) can be relentless. Operational code changes are especially prevalent because of the rapid evolution of the technology stack components. As an aside, the decoupling of operational code by a service mesh, greatly mitigates the complications of these operational code changes. This involves several of the other SOLID principles but more on this in a later article. For those interested in the service mesh, I have another article that gives a good overview (Should I Consider a Service Mesh).
If your microservice encompasses multiple responsibilities, those responsibilities are no longer independent. Due to simple probability that microservice needs to be changed more often if it has more responsibilities.
That may appear a small problem at first blush but if also affects all the other dependencies of that service. Furthermore, despite our best coding efforts, the odds of the Single Responsibility Principle also being violated with the code within the microservice are increased substantially. This would result in changes being required in the other responsibility that was not the initial target of the change. Unintended consequences likely then would cascade through its dependencies.
The microservices design principle of smart endpoints and dumb pipes is also impacted by the Single Responsibility Principle. It is not only an outcome of the principle but is subject to it as well.
Tactical Execution
Good tactics can save even the worst strategy. Bad tactics will destroy even the best strategy.
General George S. Patton
I love that quote as I have seen its truth in many different manifestations. The second sentence can be applied to our domain with this modification “bad coding will destroy even the best design”.
As mentioned earlier, the Single Responsibility Principle is extremely important in the actual coding of the microservices applications. Microservices “creep” happens when coding starts to create functionality that expand the purpose of the original design. This can happen as a result of expediting a problem resolution or from a misunderstanding of where a function should reside. Whatever the case the project management effort of team leads and architects must have rigor around the execution of development code. Code reviews with the Single Responsibility Principle as one of its checkpoints greatly improves outcomes. This is a process and a culture characteristic that takes a team to the next level of maturity.
The principle is of course still critical at the individual developer level. For example, if OOP in Java is the paradigm, the key concepts of OOP come into play:
- Abstraction
- Encapsulation
- Inheritance
- Polymorphism
The first 2 of these concepts are an embodiment of the Single Responsibility Principle. As teams grow bigger the presence of new developers increases. These team members need to be trained, mentored and evaluated on learning and executing these principles
Dogmatism and Antipattern
The Single Responsibility Principle should not be taken to the extreme. The less common but opposite problem is a microservices, class or function defined down to a granular level that defines purpose into such atomic particles that there is a boomeranging consenquence of the inability to understand the design due to the complexity of dependencies and communication pattern.
As in many things, related to system and product development a full grasping of the principle with a healthy balance of practicality is where the art in architecture and development lives.
The emergence of the hybrid, “modular monolith” is a manifestation of this practicality. It is important to discern the difference between modular monolith being intentionally deployed than one that is unintentionally arrived at through poor microservices design.
The former is rationally understood and validated, it usually involves stable and established flows, for example; combining GL, AR and AP. The latter is accidental and likely chaotic.
The principle is not the Bible.
There is a time and place where it makes sense to violate the boundary, however, they are rare. So transgressing the principle should be eyed with scrutiny and the case for this “sin” must be airtight.
What is more common is a violation of the principle with full consciousness and with the intention of correcting it later. This, of course, is the genesis of technical debt as “someday never comes”. Or worse someday arrives as a catastrophic system event.
The Single Responsibility Principle is time proven and applies through all layers of system design. I know that many of you are aware of it, but like many things fundamental, we all need to be reminded of it and conscious of it in our daily efforts.
If I can help with you in any way, please contact me through the contact page.
David Duczer deBoisblanc is the managing partner at Duczer East. David has 25 year history in the software and systems engineering domain.