In previous articles we discussed about journey of System Design evaluation from highly coupled monolith to modular monolith, flavor of highly distributed with possibility of orchestrator (workflow) driven integration model. We also discussed the goals and driving forces here which brought us to Microservice pattern. In this article, we shall dive deeper in Microservices.
Brining forward all the learning from previous articles, let us summarize the driving forces, benefits and hence the key principles of microservices architecture pattern.
Service should be releasable independently. Which means:
- It should be self contained with all the states, data and logic, and should expose its services through a well defined interface.
- It should follow encapsulation and information hiding principles well. So any change in internal implementation should not impact any client as long interface is not changed.
- Change in interfaces should mostly come with backward compatibility. Once you have active clients using the service, you don’t want to break their system with abrupt changes or by forcing them to change whenever you are making changes. It can be planned however. That’s where keeping changes backward compatible are required. For ex: keeping older operation intact, but adding new flavor and ask clients to switch to this gradually.
This single principle brings a major change in how we design the interfaces, structure the code (and teams also) and deployment models. Once we start releasing microservices independently, it brings a big shift in design thought process of teams.
- It demands definition of interfaces to be well thought through following encapsulation, information hiding principles and also encourages teams to think ahead to avoid frequent changes. When we are publishing contract on understanding of independently releasable service without impacting clients, it automatically set accountability to not to break client systems or to push clients to make abrupt changes.
- It demands well defined deployment pipelines which can push service changes to production independently without need of pulling multiple teams and consuming everyone’s time. It also means making system design simple, without complex cross dependencies.
- Independent deployments are possible only by having a strong validation test suite. Otherwise, deployment will always need verification from clients for any impact, which fails the independent release principle. Hence, it moves the teams to keep test cases suite super strong, preferably with TDD and extensive Integration, E2E test cases.
- The most important impact is sense of accountability which it instill in teams by promoting end to end ownership of services (including uptime of service). It drives teams with a clear goal and purpose.
With such great benefits on system design process and teams accountability, this is the most important principle of Microservices. If you want to start with Microservices, start with this principle. Rest all will settle in right place gradually.
Fully Contained Services based on Business Domain
Microservices structure should be driven by business domain (the domain of problem which we are trying to solve (could be technical also). Defining services with fully contained responsibilities of a domain promotes encapsulation, information hiding, end to end ownership with clear contract. It also helps to reduce cross cutting concerns and hence complex cross dependencies.
Previous principle of keeping services ‘Independently releasable’ will gradually demands for this.
Hence, design should be around business domain by slicing system in various domain areas/services. Once identified, next step is to define contract for domain interfaces and implement services to serve these. After having collection of well defined domain services, it become very easy to mix and support any simple of complex business functionality or use case.
That’s the reason Microservices promote loosely coupled smaller microservices, which can work with each other in any possible fashion to build a larger system and to serve diverse use cases. This inherently promotes power of SPR, DRY, and reusability.
When services are self contained, it means encapsulating everything required to support functionality of this domain service including frontend, controllers, business logic, and data. A common pitfall is to access data directly across service data store or use database to share the states. Which is a big anti-pattern and defeat the principle of encapsulation and the whole purpose of microservices. Once this anti-pattern creeps in, it can gradually destroy the whole system design by making services interdependent, and breaks independent release-ability. Most importantly, it could break the team accountability structure also, which is a bigger loss.
Hence, it is important to ensure proper encapsulation of data in service. Any data should be access through service interface only, which abstracts any changes for clients. There could be some special cases to handle (in large ecosystem) like for reporting, when we need data from multiple services to showcase to user or for serve any specific function etc. There are defined patterns for these, like replication or publishing data to common read-only datastore and more. It is advisable to utilize these patterns to serve such needs, rather than creating cross data connections.
Cross service boundary to access data could very well be beginning of the end of microservices benefits.
Independently Scalable, Fault Tolerant
Once services are independently deployable (preferably in containers for easy deployment and management), it also becomes inherently easy to scale these independently based on need (load, availability, failover etc) for respective service.
For ex, in below diagram, if business needs more support to manage bulk of cart exploration and order requests, more instances of Order Management service image can be instantiated immediately. While lesser used services like Customer profile and Payment management can still continue serving with less number of instances.
It also means that in case of fatal failure of any one service, for example Customer Profile Management, rest of the system can continue to serve the customers without any impact on order management, payment and delivery. In worst case, traffic of specific failed service can be routed to standby nodes in other data center too.
Possibility to scale individual service based on its own need and potential of having multiple options to manage failure within same data center or across, has a big impact on capability to serve Customer with highly scalable and fault tolerant solution while keeping Total Cost of Ownership (TCO) in check.
Freedom of Technology & Implementation Choices
Microservices are exposed through technology neutral interfaces (mostly REST or Queues or similar..). Which means, teams are free to use any technology, patterns, and data stores to support service implementation. These choices can be made based on functionality, Skills availability, Non-functional requirements and availability of time/funds for product.
Ultimate outcome is that teams have choices, flexibility of choosing technology or implementation patterns.
Flexibility is a boon to adapt to ground realities and hence to innovate for technology, product and processes, which in turn promotes effective implementation. Flexibility of choices helps in encouraging teams also to come up with best of the solution for given constraints or ground realities. This, in turn, promotes ownership, sense of purpose and belongingness.
Hence it is helpful for innovative solution, better engaged teams while keeping cost in check.
Reduced Total Cost of Ownership
Connect all the benefits listed above, and you will realize the benefits these are bringing to manage total cost of ownership by having more choices at granular level of domain features/services. With choices of scaling individual services at different level, choices of choosing right technology or framework or off the shelf solution, choices of different team structure and skills, microservices are giving more and more tools in teams hand to take best route based on available resources.
Hence, it helps in keeping TCO in check overall.
Better Organization Structure — Ownership Driven
Microservices promotes end to end ownership of service by smaller and autonomous teams, with complete accountability for service quality and availability.
Ownership driven team structure has enormous potential to transform the organization. It defines how people take ownership, how they feel connected with higher purpose of team goal and how it promotes best of the accountability. By promoting ownership, it helps to democratize decision making process and brings closer to the ground where actions happen. Decisions becomes better and hence effectiveness to overall implementation and ecosystem.
Hence, microservices pattern promotes better accountability with clear ownership structure.
*Clear ownership structure, accountability driven teams are definitely achievable without microservices as well. However, with inherent attributes of small decoupled services owned by smaller autonomous teams, microservices patterns supports it naturally, and promotes even more.
*Here is an interesting read of challenges with matrix organization, many of which can be alleviated well with accountability/ownership driven Organization structure.
With so many great benefits, microservices is definitely an architecture pattern worth exploring for products. It can bring immense benefits if adopted properly while aligning organization structure also.
However, it is still not a solution for every problem or to manage every situation. It is worth understanding various challenges which comes with such distributed architecture patterns, before taking a good informed decision. We shall discuss these in future articles.
Refer this for challenge of Troubleshooting and Monitoring in distributed environment.
Happy learning & Stay tuned for more..