Building Microservices receives mixed reviews, with many praising its comprehensive overview of microservices concepts and practical advice. Readers appreciate the author's cautious approach and real-world examples. Critics note the book's lack of depth on some topics and its potential to oversell microservices. Many find it valuable for beginners but less useful for experienced architects. The book covers various aspects of microservices, including design, deployment, testing, and scaling. Some readers wished for more concrete implementation details, while others valued its high-level perspective on software architecture.
Microservices: Small, autonomous services that work together
Evolutionary architecture: Adapting to changing requirements
Modeling services: Defining boundaries and contexts
Integration strategies: Choosing the right approach for communication
Splitting the monolith: Transitioning to microservices
Deployment techniques: Ensuring reliability and scalability
Testing microservices: Maintaining quality in a distributed system
Monitoring and security: Keeping microservices healthy and protected
Conway's Law: Aligning organization and system design
Scaling microservices: Handling growth and failure
Microservices are small, autonomous services that work together.
Foundation of microservices. Microservices architecture is built on the principle of developing software as a suite of small, independent services. Each service is focused on doing one thing well, runs in its own process, and communicates via lightweight mechanisms like HTTP/REST APIs. This approach allows for greater flexibility, scalability, and maintainability compared to monolithic architectures.
Benefits and challenges. Key advantages of microservices include:
Improved modularity
Easier scaling of individual components
Technology diversity
Enhanced fault isolation
Faster deployment cycles
However, microservices also introduce challenges such as:
Increased operational complexity
Distributed system concerns (e.g., network latency, fault tolerance)
Data consistency across services
The role of the architect is to look at the bigger picture, and understand this balance.
Embracing change. Evolutionary architecture emphasizes the need for systems to adapt to changing requirements over time. This approach recognizes that it's impossible to predict all future needs, so instead focuses on creating a flexible foundation that can evolve.
Key principles:
Incremental change: Make small, frequent updates rather than large, infrequent ones
Guided change: Use principles and practices to guide architectural decisions
Multiple architectures: Recognize that different parts of the system may evolve at different rates
Architects in this model act more as town planners, setting guidelines and constraints, rather than dictating every detail. This allows teams to make local decisions while ensuring overall system cohesion.
We focus our service boundaries on business boundaries, making it obvious where code lives for a given piece of functionality.
Domain-driven design. Modeling services effectively requires a deep understanding of the business domain. Domain-driven design (DDD) provides valuable concepts for defining service boundaries:
Bounded contexts: Areas of the domain with clear boundaries
Ubiquitous language: A common language shared by developers and domain experts
Aggregates: Clusters of domain objects treated as a unit
Identifying service boundaries:
Align with business capabilities
Encapsulate data and behavior
Minimize dependencies between services
Consider team structure and communication patterns
Well-defined boundaries lead to more cohesive services and looser coupling between them, facilitating independent development and deployment.
Be conservative in what you do, be liberal in what you accept from others.
Importance of integration. Effective integration is crucial for microservices to work together seamlessly. The choice of integration technology significantly impacts system flexibility, performance, and maintainability.
Key integration patterns:
Synchronous communication: REST, gRPC
Asynchronous communication: Message queues, event streaming
API gateways: For request routing and composition
Service mesh: For handling service-to-service communication
Best practices:
Use technology-agnostic protocols (e.g., HTTP)
Implement tolerant readers to handle changes gracefully
Design for failure with circuit breakers and bulkheads
Consider event-driven architectures for loose coupling
The right integration strategy depends on your specific use case, performance requirements, and team expertise.
Think of our monolith as a block of marble. We could blow the whole thing up, but that rarely ends well. It makes much more sense to just chip away at it incrementally.
Incremental approach. Transitioning from a monolithic architecture to microservices is best done gradually. This allows teams to learn and adapt while minimizing risk.
Steps for splitting a monolith:
Identify seams in the existing codebase
Extract bounded contexts into separate modules
Refactor shared data structures and databases
Create APIs for inter-module communication
Extract modules into separate services
Implement new features as microservices
Challenges to consider:
Data dependencies between services
Transactional integrity across service boundaries
Performance impact of network communication
Operational complexity of managing multiple services
Start with the easiest, least risky extractions to build confidence and experience before tackling more complex parts of the system.
If doing something is right but difficult, we should strive to make things easier.
Automated deployment. Reliable and scalable deployment is critical for microservices success. Continuous Integration and Continuous Delivery (CI/CD) practices are essential for managing the increased deployment complexity.
Key deployment techniques:
Infrastructure as Code (IaC)
Containerization (e.g., Docker)
Orchestration platforms (e.g., Kubernetes)
Blue-green deployments
Canary releases
Deployment considerations:
Service discovery and configuration management
Monitoring and logging
Security and access control
Database migrations and data consistency
Invest in tooling and automation to make deployments easier, faster, and more reliable. This enables teams to deploy frequently with confidence, realizing the full benefits of microservices architecture.
The more moving parts, the more brittle our tests may be, and the less deterministic they are.
Comprehensive testing strategy. Testing microservices requires a multi-layered approach to ensure both individual service quality and overall system behavior.
Testing pyramid for microservices:
Unit tests: Fast, focused tests for individual components
Integration tests: Verify interactions between services
Contract tests: Ensure services meet agreed-upon interfaces
End-to-end tests: Validate entire system behavior
Testing challenges:
Increased complexity due to distributed nature
Managing test data across services
Simulating production-like environments
Handling asynchronous interactions
Emphasize fast feedback loops with unit and integration tests, while using fewer, carefully chosen end-to-end tests to validate critical paths. Consider using consumer-driven contracts to manage service dependencies effectively.
Good logging, and specifically the ability to aggregate logs from multiple systems, is not about prevention, but can help with detecting and recovering from bad things happening.
Holistic approach. Effective monitoring and security are crucial for maintaining a healthy microservices ecosystem. These aspects become more challenging and important in distributed systems.
Monitoring best practices:
Centralized logging and log aggregation
Distributed tracing (e.g., using correlation IDs)
Real-time alerting and dashboards
Application Performance Monitoring (APM)
Synthetic monitoring for critical paths
Security considerations:
Service-to-service authentication and authorization
API gateways for edge security
Secrets management
Network segmentation
Regular security audits and penetration testing
Implement a defense-in-depth strategy, securing both the perimeter and individual services. Use automation to ensure consistent application of security policies across all services.
Conway's law highlights the perils of trying to enforce a system design that doesn't match the organization.
Organizational impact. Conway's Law states that system design mirrors communication structures within an organization. This principle has significant implications for microservices architecture.
Aligning teams and services:
Organize teams around business capabilities
Empower teams with end-to-end ownership of services
Minimize cross-team dependencies
Foster a culture of collaboration and shared responsibility
Considerations:
Team size and composition
Communication patterns and tools
Decision-making processes
Skill development and knowledge sharing
Recognize that organizational structure and system architecture are intertwined. Evolve both in tandem to create an environment conducive to successful microservices development and operation.
At scale, even if you buy the best kit, the most expensive hardware, you cannot avoid the fact that things can and will fail.
Designing for scale and resilience. Microservices architectures must be designed to handle both growth in demand and inevitable failures gracefully.
Scaling strategies:
Horizontal scaling (adding more instances)
Vertical scaling (increasing resources per instance)
Caching (in-memory, distributed)
Database sharding and replication
Asynchronous processing and event-driven architectures
Resilience patterns:
Circuit breakers to prevent cascading failures
Bulkheads for fault isolation
Timeouts and retries with exponential backoff
Graceful degradation of functionality
CAP theorem considerations:
Consistency
Availability
Partition tolerance
Understand that trade-offs are necessary when scaling distributed systems. Prioritize based on your specific requirements and constraints. Implement observability and chaos engineering practices to continuously improve system resilience.