Building Microservices
Microservices: What and Why
- Microservices definition: Build small, autonomous services that work together, focused around business domains
- Size consideration: Keep services small enough to be understood by a single team
- Independence principle: Design services that can be changed and deployed independently
- Business alignment: Align services with business domains, not technical layers
- Evolutionary architecture: Embrace microservices as an evolutionary architecture
- Technology diversity: Enable teams to choose the right technology for specific services
- Resilience improvement: Design for failure to improve system resilience
- Scaling benefits: Scale components independently based on their specific requirements
- Organizational alignment: Structure teams around services to optimize ownership and delivery
- Complexity trade-off: Recognize that microservices trade simple components for complex interactions
Strategic Goals and Principles
- Business goals first: Start with business goals, not technology choices
- Evolutionary approach: Take an incremental approach to adoption
- Domain boundaries: Identify proper service boundaries using domain-driven design
- Independent deployability: Make independent deployment a core requirement
- Service ownership: Assign clear ownership for each service
- Interface design: Design clean, well-defined service interfaces
- Versioning strategy: Establish versioning approaches early
- Standardization balance: Standardize where it reduces complexity, customize where it adds value
- Automation necessity: Treat automation as a requirement, not a luxury
- Failure design: Design explicitly for failure scenarios
Evolutionary Architect Role
- Enable vs. control: Focus on enabling teams rather than controlling them
- Technical vision: Define and communicate clear technical vision
- Governance establishment: Create governance that enables autonomy with alignment
- Principles over details: Focus on principles and practices, not implementation details
- Standard platforms: Build or select standard platforms to reduce cognitive load
- Team autonomy: Allow teams significant autonomy within defined constraints
- External focus: Keep aware of industry trends and evolving practices
- Experience sharing: Facilitate cross-team experience sharing
- Technical debt strategy: Create strategies for handling technical debt
- Experimentation encouragement: Support controlled experimentation
Modeling Services
- Domain-driven design: Apply DDD principles to identify service boundaries
- Bounded contexts: Use bounded contexts to define service scope
- Business capability focus: Organize around business capabilities, not technical functions
- Cohesion principle: Maximize cohesion of related functionality
- Coupling minimization: Minimize coupling between services
- Interface stability: Design stable interfaces that hide implementation details
- Service responsibilities: Make each service responsible for a single part of the domain
- Data ownership: Make services own their data and behavior
- Shared models caution: Minimize shared models between services
- Premature decomposition: Avoid overly fine-grained services early on
Integration Approaches
- API design: Design APIs with consumers in mind
- Technology agnosticism: Choose integration technology that doesn’t lock consumers
- Synchronous caution: Be cautious about synchronous dependencies
- REST consideration: Consider REST for service-to-service communication
- Chattiness avoidance: Design coarse-grained interfaces to avoid chattiness
- Event-driven patterns: Consider event-driven collaboration for looser coupling
- Choreography vs. orchestration: Prefer choreography over orchestration where appropriate
- Versioning strategy: Implement clear API versioning strategies
- Client libraries: Use client libraries judiciously, aware of their trade-offs
- Integration testing: Create targeted integration tests between services
Splitting the Monolith
- Incremental approach: Decompose monoliths incrementally, not all at once
- Seam identification: Find natural seams in your codebase for service boundaries
- Database decomposition: Break apart the database alongside the code
- Staged migration: Use strangler pattern for staged migration
- Reporting challenges: Address reporting across services early
- Transactional boundaries: Redefine transactional boundaries when splitting services
- Refactoring preparation: Refactor toward service boundaries before extracting
- Feature toggles: Use feature toggles to enable incremental switchover
- Coexistence period: Plan for a period of monolith and microservices coexistence
- Team alignment: Align team structure with the planned service decomposition
Deployment Approaches
- Automation requirement: Automate deployment processes completely
- Immutable infrastructure: Treat servers as immutable and disposable
- Environment consistency: Keep environments as consistent as possible
- Configuration management: Externalize all configuration from deployable artifacts
- Continuous integration: Practice continuous integration for all services
- Continuous delivery: Implement continuous delivery pipelines
- Platform selection: Choose deployment platforms that support microservice architectures
- Container usage: Leverage containers for deployment consistency
- Service discovery: Implement service discovery mechanisms
- Deployment isolation: Isolate service deployments from each other
Testing Microservices
- Test pyramid: Implement a balanced test pyramid
- Unit test focus: Focus heavily on unit tests for microservices
- Test scope: Scope tests appropriately to validate behavior, not implementation
- Consumer contracts: Use consumer-driven contracts for service interactions
- End-to-end caution: Use end-to-end tests sparingly and strategically
- Service virtualization: Leverage service virtualization for testing
- Production testing: Consider testing in production with appropriate safeguards
- Automated verification: Automate as much verification as possible
- Test environments: Create right-sized test environments for different purposes
- Performance testing: Conduct performance testing at both service and system levels
Monitoring and Observability
- Semantic monitoring: Implement monitoring that understands business processes
- Standard metrics: Define standard metrics across all services
- Correlation IDs: Use correlation IDs to track requests across services
- Centralized logging: Aggregate logs in a central, searchable location
- Health checks: Implement standardized service health checks
- Alerting strategy: Create clear, actionable alerting with minimal noise
- Dashboards: Build dashboards for both technical and business metrics
- Synthetic transactions: Use synthetic transactions to verify end-to-end functionality
- Circuit breaking: Implement circuit breakers for failing dependencies
- Degraded functionality: Design services to function in a degraded mode when dependencies fail
Security Considerations
- Defense in depth: Implement security at multiple layers
- Least privilege: Follow principle of least privilege for service-to-service communication
- Perimeter security: Don’t rely solely on perimeter security
- Credential protection: Protect service credentials carefully
- Service authentication: Implement service-to-service authentication
- Token-based security: Consider token-based security for service calls
- Transport security: Encrypt all service communication
- Secrets management: Use dedicated secrets management solutions
- Attack surface: Be aware of expanded attack surface with microservices
- Security testing: Include security in your testing strategy
Managing Service Boundaries
- API gateway: Consider API gateways for external client communication
- Backend for frontend: Implement BFF pattern for different client types
- Client integration: Plan for various client integration patterns
- UI composition: Design for UI composition across services
- Service mesh: Evaluate service mesh for cross-cutting concerns
- Rate limiting: Implement rate limiting for API consumers
- Documentation: Keep API documentation current and accessible
- Breaking changes: Minimize breaking changes to APIs
- Contract evolution: Evolve contracts carefully to maintain compatibility
- Deprecation policies: Establish clear API deprecation policies
Conway’s Law and Team Organization
- Team-service alignment: Align team boundaries with service boundaries
- Team autonomy: Give teams autonomy over their services
- Team size: Keep teams small enough to be fed by two pizzas
- Cognitive load: Consider team cognitive load when assigning services
- Internal open source: Treat shared code as internal open source
- Communities of practice: Create communities of practice across teams
- Specialist collaboration: Define how specialists collaborate across teams
- Decision delegation: Push decision-making authority to teams
- Capability development: Develop capabilities needed for team independence
- Communication facilitation: Facilitate communication across team boundaries
Data Management and Migration
- Data ownership: Make each service own its data
- Shared database avoidance: Avoid shared databases between services
- Schema evolution: Plan for schema evolution within services
- Query complexity: Address cross-service query complexity
- Event sourcing: Consider event sourcing for complex domains
- CQRS pattern: Evaluate CQRS for read/write separation
- Data migration: Plan incrementally for data migration during decomposition
- Referential integrity: Redefine referential integrity across service boundaries
- Eventual consistency: Embrace eventual consistency where appropriate
- Transaction boundaries: Design new transaction boundaries for distributed systems
Scaling Microservices
- Horizontal scaling: Design services for horizontal scaling
- Stateless preference: Prefer stateless services when possible
- Caching strategy: Implement appropriate caching strategies
- Load balancing: Use load balancing across service instances
- Database scaling: Address database scaling alongside service scaling
- Autoscaling: Implement autoscaling based on appropriate metrics
- Scale cube: Consider scale cube dimensions (X, Y, Z) for different scaling approaches
- Bottleneck identification: Identify and address scaling bottlenecks
- Failure isolation: Design scaling for failure isolation
- Cost efficiency: Balance scaling for both performance and cost efficiency
Organizational Challenges
- Culture considerations: Address cultural aspects of microservice adoption
- Learning investment: Invest in learning and skill development
- Hiring strategy: Adjust hiring for microservice-specific skills
- Incremental adoption: Take an incremental approach to organizational change
- Knowledge sharing: Create mechanisms for cross-team knowledge sharing
- Success celebration: Celebrate and publicize early successes
- Failure tolerance: Create a culture that tolerates controlled failure
- Metrics alignment: Align team and organizational metrics
- Leadership support: Secure ongoing leadership support for transformation
- Patience practice: Exercise patience during the transition period
Key Takeaways
- Business domain focus: Align services with business domains and capabilities
- Incremental evolution: Take an evolutionary approach to microservice adoption
- Team ownership: Align teams with services for clear ownership and autonomy
- Independent deployability: Make services independently deployable and releasable
- Resilience design: Design explicitly for failure with graceful degradation
- Automation investment: Invest heavily in automation for build, test, and deployment
- Observability priority: Make monitoring and observability first-class concerns
- Interface stability: Design stable interfaces that hide implementation details
- Data ownership: Make services own their data to maintain clear boundaries
- Organization alignment: Align organization structure with desired architecture