Introduction
Without a doubt, Sam Newman remains the authority on Microservices. I certainly enjoy his books and without them I would not be where I am today. And he of course has his own set of 7 principles of microservices. I will list them here in no particular order.
- Deploy Independently
- Decentralise All the things
- Hide Implementation detail
- Culture of automation
- Modelled around a business domain
- Highly observable
- Isolate failure
I do find these principles useful. Some are very obvious, but others are a little bit more abstract and open to interpretation.
When I first began my microservices journey I found it helpful come up with my own set of principles (based on Sam Newman’s book) to help guide me in creating microservice architectures, and I have been working with them ever since. Today I would like to share these principles with you and go over them in a little more detail. So here are my 12 principles for designing a microservice architecture
- A microservices should be independently deployable
- Services should be loosely coupled and not know anything about each other
- A microservice architecture should prefer choreography over orchestration
- A services should own it’s own data
- A services should output comprehensive logs
- Microservices should be platform agnostic and avoid vendor lock-in
- It should be easy to scale a service
- A services should expose metrics from the beginning
- The architecture should avoid putting business logic into 3rd party systems
- It should be easy for a human to see what is going on
- Cross domain reporting should be offloaded to another system
- A Service should be built around a bound context or domain
Let’s now look at each of these principles in turn and discuss just why it is important and how it guides the design of an architecture
Principle 1: A microservices should be independently deployable
The principle of independence in microservices architecture is foundational. It hinges on the concept that each microservice operates as a self-contained unit, responsible for a specific business function. This independence is not just about the service itself but extends to the entire lifecycle, including development, deployment, and maintenance. By designing each service this way, organizations can achieve a high degree of flexibility and agility, enabling them to respond more rapidly to market changes or customer needs.
Independence in microservices goes hand-in-hand with the concept of independent deployability. This means that each microservice can be deployed, updated, scaled, and restarted independently of others in the system. This feature is crucial in modern software development, where the need for frequent updates and quick rollbacks is common. Independent deployability allows for faster iteration cycles, as teams can release updates to individual services without the need for coordinated deployments across the entire application. This leads to reduced downtime and a more resilient system overall.
From a technical standpoint, achieving independent deployability often involves containerization technologies like Docker and orchestration tools like Kubernetes. These technologies encapsulate microservices in their own environments, complete with all the necessary dependencies, ensuring that they can run independently across different computing environments. This setup also simplifies scaling and load balancing, as services can be dynamically scaled up or down based on demand, without impacting other parts of the system.
However, achieving true independence in microservices is not without its challenges. It requires a well-thought-out design that considers aspects like service boundaries, data management, and inter-service communication. Careful planning is necessary to ensure that services are not too granular, leading to communication overhead, or too large, which can negate the benefits of the microservices architecture. Nonetheless, when implemented effectively, the principle of independent and independently deployable microservices can significantly enhance the agility and resilience of software applications, making it a cornerstone of modern software architecture.
Principle 2: Services should be loosely coupled
The second principle of microservices architecture emphasises the importance of loose coupling between services. Loose coupling means that each microservice is designed to operate independently, with minimal knowledge or dependency on other services in the system. This approach is essential for creating a resilient, flexible architecture that can adapt to changes without significant rework.
In practice, loose coupling also involves adopting patterns like asynchronous communication and event-driven architectures. These patterns enable services to interact without requiring immediate responses, further decoupling their operations. For instance, a service can publish an event when a particular action is completed, without needing to know which other services will consume this event. This method of communication enhances the system’s ability to handle varying loads and maintain functionality even if one or more services are down.
However, implementing loose coupling requires careful consideration of service boundaries and responsibilities. It’s crucial to strike a balance between making services too interdependent and creating too many fine-grained services, which can lead to complexity in communication and management. Additionally, designing for loose coupling often requires a shift in the development mindset, as developers need to think in terms of contracts and interactions rather than direct service-to-service dependencies.
Principle 3: Prefer choreography over orchestration
The third principle of microservice architecture is the preference for choreography over orchestration in managing service interactions. This principle is pivotal in shaping the way microservices communicate and coordinate with each other, impacting the overall agility and resilience of the system.
Choreography refers to a decentralized approach to service interaction. In a choreographed system, each microservice is aware of its role and acts independently based on events or messages it receives. Unlike orchestration, where a central conductor (often a separate service or manager) dictates the workflow and controls the communication between services, choreography allows for a more organic flow of information and decision-making. Services in a choreographed architecture publish events or messages that other services can subscribe to and react upon, creating a dynamic, self-organizing system.
This approach offers several advantages. First, it reduces the complexity and potential bottlenecks associated with having a central orchestrator. By enabling services to work autonomously, the system becomes more resilient to failures. If one service goes down, it does not cripple the entire workflow, as there is no single point of failure. Additionally, choreography supports better scalability, as services can be added or removed without reconfiguring a central coordinator.
Principle 4: A services should own it’s own data
The fourth principle of microservice architecture underscores the importance of data autonomy: each service should own its own data. This principle is fundamental in ensuring that microservices are truly independent, scalable, and secure, which are key attributes for an efficient and robust system.
Data ownership in microservices means that each service is responsible for its own database or data storage mechanism. This approach stands in contrast to traditional monolithic architectures, where a single database often serves multiple components or services. In a microservice architecture, services interact with their own data and do not share databases with other services. This isolation ensures that changes in one service’s data schema or storage technology do not impact others, fostering a more resilient and adaptable system.
Owning its own data allows each service to be independently developed, deployed, scaled, and maintained. This independence reduces the complexity of deployments and updates, as changes in one service do not necessitate adjustments in others. Furthermore, it enhances the security of the system. By limiting data access to the service that owns it, the risk of data breaches or leaks across services is minimized.
However, implementing this principle comes with its challenges. It requires careful planning to ensure data consistency and integrity across the system. Services often need to interact and share data, which is typically achieved through well-defined APIs or asynchronous messaging systems. Ensuring that data remains consistent across these interactions, especially in distributed transactions, can be complex. Moreover, data duplication across services can lead to issues of data synchronisation and increased storage requirements.
Principle 5: A services should output comprehensive logs
The fifth principle of microservice architecture emphasizes the importance of comprehensive logging in each service. In a distributed system, where numerous services work in tandem, maintaining detailed logs is not just beneficial—it’s crucial for system health, debugging, and monitoring. This principle underlines the need for services to record detailed information about their operations, interactions, and any anomalies that occur.
Comprehensive logging in microservices involves capturing and storing detailed information about each service’s activities. This includes data on requests handled, responses sent, internal process states, and errors or exceptions encountered. Unlike in monolithic architectures where a single log can capture the entirety of the application’s behavior, microservices must each maintain their own logs due to their distributed nature. This decentralized approach to logging presents unique challenges but also offers significant advantages in terms of granularity and fault isolation.
One of the key benefits of comprehensive logging is enhanced debugging and troubleshooting. In a distributed system, identifying the root cause of a problem can be like finding a needle in a haystack. Detailed logs from each service provide invaluable insights into the system’s state at any given moment, making it easier to trace issues back to their source. This granularity not only speeds up the debugging process but also improves system reliability by enabling quicker resolution of issues.
However, implementing effective logging in a microservice architecture requires careful consideration. It’s important to strike a balance between the level of detail and the volume of logs generated. Excessively verbose logs can lead to information overload, making it difficult to extract meaningful insights. On the other hand, insufficient logging can leave gaps in understanding the system’s behavior. Adopting structured logging formats, such as JSON, can facilitate easier parsing and analysis of logs. Additionally, leveraging log aggregation and analysis tools can help manage and make sense of the vast amount of data generated.
Another critical aspect is ensuring the security and privacy of the logged information. With services potentially handling sensitive data, logs must be managed in a way that complies with data protection regulations and organizational policies. This includes implementing proper access controls, encryption, and ensuring that sensitive data is not inadvertently logged.
Principle 6: Be platform agnostic and avoid vendor lock-in
The sixth principle of microservice architecture underscores the significance of developing services that are platform agnostic and free from vendor lock-in. This principle advocates for creating microservices that can operate on various platforms and infrastructures, ensuring flexibility and adaptability in a rapidly evolving technological landscape.
Platform agnosticism in microservices means designing services that are not tightly bound to any specific vendor’s technology, infrastructure, or services. This approach enables microservices to be deployed across different environments, whether on-premises, in the cloud, or across multiple cloud providers. It empowers organizations to choose the best platforms for their needs without being restricted by the limitations of a particular vendor or technology stack. This versatility is particularly beneficial in scenarios where requirements or preferences for deployment environments change over time.
Avoiding vendor lock-in is a strategic move that ensures long-term flexibility and control over the technology stack. Vendor lock-in occurs when a service is so tightly coupled with a vendor’s specific technology or infrastructure that migrating to another platform becomes costly or infeasible. This can lead to several issues, including limited negotiation power, dependency on the vendor’s roadmap and pricing models, and challenges in adapting to new technologies or scaling the services as needed.
To achieve platform agnosticism and avoid vendor lock-in, microservices should be designed with portability in mind. This involves using open standards and protocols, abstracting away vendor-specific features, and ensuring compatibility with different environments. For instance, containerization technologies like Docker and orchestration tools like Kubernetes play a pivotal role in enabling this flexibility. They allow microservices to be packaged and managed in a way that is independent of the underlying infrastructure.
However, achieving this level of independence does not come without its challenges. It requires a careful balance between leveraging the unique advantages of a specific platform and maintaining the ability to migrate services with minimal effort. Teams must be aware of the potential trade-offs, such as the additional complexity of abstracting services from platform-specific features or the possible underutilisation of advanced capabilities offered by certain vendors.
Principle 7: It should be easy to scale a service
The seventh principle of microservice architecture emphasizes the importance of scalability in service design. In today’s fast-paced and ever-changing digital landscape, the ability to efficiently scale services in response to varying demand is crucial for maintaining performance, managing costs, and ensuring user satisfaction.
Scalability in the context of microservices refers to the capability of a service to handle increased load by either scaling up (adding more resources to the existing infrastructure) or scaling out (adding more instances of the service). An effectively scalable service can adapt to fluctuating demands without significant manual intervention, ensuring consistent performance even under varying loads.
The need for easy scalability stems from the unpredictable nature of web traffic and user demand. Services that cannot scale appropriately may suffer from reduced performance or even downtime during peak loads, leading to a negative user experience. Conversely, over-provisioning resources to handle peak loads can be cost-inefficient during off-peak times. Thus, a scalable microservice architecture offers a balance, dynamically adjusting resources to meet current demands.
Designing for scalability involves several considerations. Firstly, services should be stateless where possible, allowing them to be easily replicated and distributed across multiple servers or nodes. Stateless services do not retain user-specific data from one session to another, making it easier to add or remove instances without impacting the user experience. Secondly, efficient load balancing strategies are crucial. Load balancers distribute traffic evenly across multiple service instances, preventing any single instance from becoming a bottleneck.
Furthermore, implementing auto-scaling policies is a key aspect of easy scalability. Auto-scaling automatically adjusts the number of service instances based on predefined metrics, such as CPU usage, memory consumption, or request rate. This ensures that the service can handle spikes in demand without manual intervention. Using container orchestration platforms like Kubernetes can significantly simplify the implementation of such policies.
However, designing for scalability also presents challenges. It requires careful planning and testing to ensure that services can scale effectively without encountering issues such as database bottlenecks, network latency, or synchronisation problems. Developers must also consider the cost implications of scaling, as indiscriminate scaling can lead to increased operational costs.
Principle 8: A services should expose metrics from the beginning
The eighth principle of microservice architecture underscores the importance of exposing metrics from the onset of a service’s deployment. This principle is pivotal for maintaining the health, performance, and reliability of services in a microservices-based system.
Exposing metrics involves the continuous collection and reporting of data related to the performance and usage of a service. These metrics can include a wide range of information, such as response times, error rates, system throughput, and resource utilization (CPU, memory, disk I/O). By making these metrics available from the beginning, teams can gain valuable insights into how their services are performing in real-world scenarios.
The rationale behind this principle lies in the inherent complexity and distributed nature of microservice architectures. In such environments, pinpointing the root cause of issues can be challenging due to the interactions between multiple, independently deployable services. Metrics provide a quantifiable and objective way to monitor the system’s health and quickly identify problems. They are essential for proactive maintenance, allowing teams to detect and address issues before they escalate into more significant problems.
Implementing effective metrics exposure requires a well-thought-out strategy. It begins with determining which metrics are most relevant to the service and its stakeholders. These metrics should be comprehensive enough to provide a clear picture of the service’s performance but focused enough to avoid overwhelming teams with unnecessary data. Common practices include using standardized metric formats and protocols for ease of aggregation and analysis.
In addition to choosing the right metrics, the integration of effective tooling is crucial. Tools for monitoring, logging, and alerting should be incorporated into the service from the outset. These tools can range from basic logging mechanisms to more sophisticated monitoring solutions like Prometheus or Grafana. They enable the collection, storage, and visualization of metrics, providing teams with the ability to analyze trends, set up alerts for anomalous behavior, and conduct post-mortem analyses when issues arise.
However, exposing metrics is not without its challenges. Care must be taken to ensure that the collection and reporting of metrics do not significantly impact the performance of the service itself. Additionally, there are security and privacy considerations, especially when dealing with sensitive data. Teams must implement proper safeguards to protect metric data and ensure compliance with relevant regulations and standards.
Principle 9: Avoid putting business logic into 3rd party systems
The ninth principle of microservice architecture focuses on a crucial aspect: avoiding the placement of business logic in third-party systems. This principle emphasizes the importance of maintaining control over the core functions and operations that define the service’s purpose and value.
Business logic refers to the computational processes that handle the data of a business and enforce its business rules and policies. This can include algorithms, calculations, data processing routines, and decision-making mechanisms specific to a business’s needs. By keeping this logic within the boundaries of the service rather than in external systems, organizations ensure greater control, flexibility, and security over their critical operations.
The rationale behind this principle stems from the risks and limitations associated with relying on third-party systems for business logic implementation. Third-party systems often come with constraints in terms of customization and scalability. Changes in these systems, whether in terms of features, policies, or pricing, can have a direct and sometimes disruptive impact on the services that depend on them. Moreover, when business logic resides in external systems, it becomes challenging to maintain a consistent and seamless integration, especially when dealing with multiple services in a microservices architecture.
Another significant concern is the dependency risk. Relying on third-party systems for core business logic can lead to scenarios where the availability and reliability of the service are at the mercy of external factors. This can pose a significant threat in situations where downtime or degraded performance in the third-party system can directly impact the service’s functionality and user experience.
The implementation of this principle involves careful architecture planning. Services should be designed to encapsulate their business logic, ensuring that any external dependencies are kept to a minimum and are abstractly managed. This approach not only reduces the risk of external dependencies but also enhances the portability and adaptability of the service. It allows for easier updates and changes to the business logic without the need to coordinate with external vendors or adjust to their limitations.
However, this principle does not advocate for complete isolation from third-party systems. In many cases, leveraging external services and platforms for non-core functionalities, such as authentication, payment processing, or analytics, can be beneficial and cost-effective. The key is to ensure that the core business logic, the essence of the service’s functionality, remains within the control of the organisation.
Principle 10: It should be easy for a human to see what is going on
The tenth principle of microservice architecture is centered around the critical aspect of human comprehensibility. This principle asserts that the design and operation of microservices should be transparent and understandable, enabling developers, operators, and even end-users to easily grasp what is happening within the system. This clarity is essential for effective management, troubleshooting, and future development of the services.
In the complex ecosystem of microservices, where numerous services interact in potentially intricate ways, maintaining a design that is easily decipherable is crucial. This involves creating services and their interactions in a way that they are intuitive and their functionalities and dependencies are clearly identifiable. Simple, well-documented interfaces and processes not only facilitate smoother onboarding of new team members but also aid in faster identification and resolution of issues when they arise.
Certainly! Let’s explore the tenth principle of Microservice architecture, “It should be easy for a human to see what is going on,” in a detailed manner:
Enhancing Human Comprehensibility: The Tenth Principle of Microservice Architecture
The tenth principle of microservice architecture is centered around the critical aspect of human comprehensibility. This principle asserts that the design and operation of microservices should be transparent and understandable, enabling developers, operators, and even end-users to easily grasp what is happening within the system. This clarity is essential for effective management, troubleshooting, and future development of the services.
In the complex ecosystem of microservices, where numerous services interact in potentially intricate ways, maintaining a design that is easily decipherable is crucial. This involves creating services and their interactions in a way that they are intuitive and their functionalities and dependencies are clearly identifiable. Simple, well-documented interfaces and processes not only facilitate smoother onboarding of new team members but also aid in faster identification and resolution of issues when they arise.
There are several techniques that can be employed to enhance the visibility of what is happening within a microservice architecture. These include:
- Effective Logging: Implementing comprehensive logging that captures significant events and states within each service. These logs should be structured in a way that they are easily queryable and interpretable.
- Monitoring and Dashboards: Using monitoring tools and dashboards that provide real-time insights into the health and performance of each service. These tools should be capable of aggregating data from different services to present a cohesive view of the system’s status.
- Documentation and Diagrams: Maintaining up-to-date documentation and architecture diagrams that depict the roles, responsibilities, and connections of each service. These resources should be easily accessible and understandable.
While striving for simplicity, it is also important to recognize that some level of complexity is inherent in any microservice architecture. The key is to manage this complexity without compromising clarity. Services should be designed with a focus on doing one thing and doing it well, following the principle of single responsibility. This approach helps in reducing cognitive overload and makes it easier for individuals to understand and work with each service.
The principle also advocates for a human-centric design approach, where the needs and capabilities of the people interacting with the system are given paramount importance. This involves considering factors like ease of use, learning curve, and the mental model of the users. User feedback should be actively sought and incorporated into the design and operational processes, ensuring that the system remains aligned with the needs and expectations of its human users.
Principle 11: Cross domain reporting should be offloaded to another system
The eleventh principle in microservice architecture highlights the strategic separation of cross-domain reporting from the primary functions of microservices. It advocates for offloading reporting tasks, especially those that span multiple domains, to dedicated systems. This principle plays a pivotal role in optimising performance, maintaining clarity, and ensuring scalability in microservice environments.
Microservices are typically designed to perform specific, narrowly defined tasks within their domain. When reporting requirements span across multiple domains, they can introduce complexities and dependencies that contradict the microservice philosophy of independence and single responsibility. By offloading cross-domain reporting to a specialized system, microservices can maintain their focus on core functionalities, leaving the aggregation and analysis of data across domains to a system better suited for this purpose.
Offloading cross-domain reporting offers several benefits:
- Reduced Complexity: It simplifies each microservice, as they no longer need to handle the intricacies of reporting across multiple domains.
- Improved Performance: Microservices can operate more efficiently as they are not bogged down by resource-intensive reporting tasks.
- Enhanced Scalability: Scalability becomes easier to manage when the reporting load is handled by a system designed for such tasks, allowing microservices to scale independently.
- Better Data Management: A dedicated reporting system can offer more advanced data processing and analysis capabilities, providing richer insights.