Principles Of The Microservice Architecture (Part 1)
Learning The Microservice Design Paradigm
Hi there! I hope you all are doing well. In the last newsletter, I discussed some of the fundamentals of System Design. The most basic building blocks are Microservices. In this newsletter, I will go deeper into understanding the core principles of Microservice Architecture. To make it easy to read, I have broken down this newsletter into two parts. The next part will be available by Sunday. Let’s get started then!
Introduction
The Microservice Architecture is a design paradigm where a large complex application is broken down into a suite of small function component services that have the following characteristics:
Loosely coupled
Highly maintainable and testable
Independently developed, deployed, and maintained
Organized around business capabilities
Owned by a small team
The image above (Courtesy: Edureka. co) shows the primary distinction between a Monolithic, Service Oriented, and Microservices Architecture. The monolithic architecture consists of a single large component that encapsulates the application. The Service-Oriented Architecture (also called SOA) breaks the services into coarse-grained modules while the Microservices architecture breaks them into very small granules.
Principles Of The Microservice Architecture
This section will delve deeper into the core seven principles required for a Microservice Architecture to work well.
Domain-Driven Design
A typical application usually caters to a collection of business domains. A domain is an independent area of a problem space that has abounded context and emphasizes a common language to talk about the problems.
Let’s take the example of a typical credit card processing application. In such an application, there are primarily three different domains in the payment processing workflow. These are as follows:
Merchant Subdomain: Handles financial parameters & merchant acceptance workflow.
Accounting Subdomain: Handles end-of-day transaction processing, clearance, & reconciliation.
Payment Subdomain: Handles online transaction processing.
Given the above subdomains, the application can be broken down into multiple microservices, and separation of concerns can be thus established.
Microservices are, therefore, organized and built around encapsulated business capabilities. A business capability represents the function that the business unit does in a particular domain. Incorporating Domain-Driven Design allows the architecture to isolate system ability into various domains.
Culture Of Automation
Slicing up a monolithic application into a suite of component services in a highly decomposed ecosystem naturally lends itself to independently deployable units. Organizations that adopt the microservices architecture should adopt practices that lead to automation of microservices. Some of the functions that can be automated include builds, testing, deployments, alerts, etc., across microservices.
Infrastructure Automation
Infrastructure as Code (IaC) is configuring and managing the infrastructure through a descriptive model. It is all about treating your infrastructure configuration and provisioning the same way you treat your application source code. The last decade has seen significant advancements in IAAC (Infrastructure As A Code). Tools such as Terraform, Ansible, AWS CloudFormation, Azure Resource Manager, Google Cloud Deployment Manager, etc., make the provisioning of infrastructure (servers, load-balancers, caches, databases, etc.) seamless.
Testing Automation
Given the decentralized model, the microservice architecture requires rigorous testing to ensure that the system is stable when released. This requires investment from many different tools to ensure that the Microservices are sufficiently tested pre-release. Some of these tools include Apache JMeter, Gatling, Jaeger, Cypress, etc. These tools help teams write scalability, end-to-end tests and monitor and observe the system behavior when the tests are run.
Continuous Delivery
Continuous delivery is a software engineering approach in which teams produce software in short cycles, ensuring that the software can be reliably released at any time and, when removing the software, without doing so manually. It aims at building, testing, and releasing software with more incredible speed and frequency. To make the delivery of applications seamless and low-overhead, it is necessary that each of the microservices can be deployed through the continuous delivery model. In this model, teams need to invest in testing automation and delivery platforms such as Harness, Spinnaker, Jenkins, Gitlab, etc.
Encapsulation: Hiding Implementation Details
The principle of encapsulation states that a service should only be consumed through a standardized API and that it shouldn’t expose its internal implementation details (business logic, auth implementation, persistence logic, etc.) to its consumers. Let’s consider the design of a system with the following two strategies:
Design 1
A single shared database with multiple services accessing it.
Design 2
Each service owns its database schema and exposes interactions through APIs.
In design 1, changes to the database schema such as deleting a column, changing the column’s data type, and adding a new column will most probably require changes upstream to many different services. This can cause unknown ripple effects, introduce bugs and require high maintenance. Therefore going with design 2 makes more much more sense.
How To Design For Encapsulation
We will talk about two important concepts, namely APIs and Bounded Contexts, to better understand the idea of encapsulation and separation of concerns.
Application Programming Interfaces
Application Programming Interfaces, or APIs commonly referred to, are interfaces defined between components for exchanging information based on particular actions. Commonly APIs are designed to follow the CRUD (Create, Read, Update, and Delete) model. APIs expose only a limited set of information and in a standard contract that hides the complexity of the underlying data model, persistence layer, networking architecture, etc.
Bounded Contexts
Bounded Contexts are a class of components that share a shared understanding of concepts. They are a central pattern in Domain-Driven Design (DDD). DDD deals with large models by dividing them into different Bounded Contexts and being explicit about their interrelationships.
Let’s refer to the image above. There are two different bounded contexts. These are the Sales Context and the Support Context. The Sales Context deals with concepts such as Opportunity, Pipeline, Territory, Sales Person exclusively. The ideas such as Customer and Product are shared between Sales and Support Contexts. Similarly, the Support Context understands concepts such as Ticket, Defect, Product Version exclusively, and Customer, Product in a shared manner.
Why Is Bounded Context Important To Consider
It is essential to understand that the same concept, such as a Customer, may have a different meaning based on their context. Let’s take an example. In the Sales Context, the Customer may mean a client I have sold my product to, while in the Support Context, it may refer to a client who has raised a support ticket. While designing the APIs, it is essential to consider what information to share vs. what information to hide. It is easier to share confidential details incrementally than to hide information that is already shared. If the Support Context only requires information about the tickets that a customer has raised in the past, then sharing the complete customer object may not be a good idea.
Decentralization
Let’s first understand why decentralization is needed in a microservice architecture. To build highly scalable and resilient systems, individual component owners require autonomy. Autonomy is giving people as much freedom as possible to do the job at hand. Some of the common principles to follow when building a decentralized architecture are as follows:
Self-Service
The idea of self-service consists of providing the teams the ability to provision resources (such as virtual machines, service instances, storage units, etc.). Traditionally, the cycles to get any resource provisioned would require teams to raise tickets; the ticket may have to go through approval from the IT department. This would significantly slow down the overall process and make the process of software development slow. With the advent of the DevOps model, and the availability of hyper-scale cloud platforms such as AWS, GCP, Azure, etc., it has become significantly easier for teams to be self-served and managed.
Shared Governance
Centralized governance suffers from the problem of solving every problem using the same hammer. They work on the philosophy of standardizing on single technology platforms even though there’s never one right for all jobs. Splitting a Monolith into several microservices that are loosely coupled services allows conscious and customized tech-stack choices when building each of them, typically along the lines of what fits the job at hand best.
This article by the team at Gilt talks about a shared governance architecture, where they disbanded a “central architecture team.“ Under this model, each microservice team has its own set of “architects.” They then comprise an “architecture board.” Their general purpose is to ensure the sharing of best practices and to identify the very few standards we need to govern– mainly where technology crosses team boundaries (e.g., how software built by one team talks with software built by another team).
Smart Endpoints And Dumb Pipes
The microservice architecture posits that the business logic should be encapsulated within the microservices. The communication medium (such as message buses) should be dumb. This is very different from SOA’s Enterprise Service Bus (ESB) model, which includes sophisticated features of message routing, data transformation rules, understanding of the business context, etc., in the message bus. The major drawback of making the pipes smart tends to be that changes in the business logic will require downstream changes in microservices, message buses, and other microservices. This can lead to significant bugs in the system and increase the cost of incremental changes and maintenance.
References
Maximizing Microservices Architecture Part 1: The What and Why
Top 10 Infrastructure as Code Tools to Boost Your Productivity
Cheers,
Ravi.
thanks for sharing the insightful links.