5. Microservices Architecture and Design PatternsIntroduction and ContextMicroservices architecture is an approach to developing a software system as a collection of independently deployable services. Each service represents a relatively cohesive business capability, owns its implementation and, ideally, controls its data. Services communicate through explicit interfaces, commonly HTTP APIs, asynchronous messaging, or event streams. Microservices are not simply a method of dividing a large application into smaller programs. They change how teams design boundaries, manage data, test systems, deploy software, observe production behaviour, and govern change. The architectural objective is not maximum fragmentation; it is the creation of independently evolvable components whose boundaries correspond to meaningful business capabilities. This chapter examines design patterns that help organizations introduce, integrate, operate, and gradually modernize microservices. It also considers anti-patterns, quality attributes, architecture decision records, and the trade-offs that accompany distributed systems. The discussion assumes familiarity with RESTful APIs, HTTP, databases, software architecture, and basic deployment practices. Learning ObjectivesBy the end of this chapter, the learner should be able to: 1. Explain the motivation, structure, and essential properties of microservices architecture. 2. Select and apply API gateway, backend-for-frontend, service discovery, and sidecar patterns. 3. Explain migration patterns such as strangler fig, anti-corruption layer, branch-by-abstraction, and parallel run. 4. Identify distributed-system risks and common microservices anti-patterns. 5. Evaluate architectural alternatives using quality attributes and explicit trade-offs. 6. Prepare an architecture decision record for a microservices-related decision. 7. Apply these ideas to professional system design and modernization scenarios. From Modular Systems to Distributed ServicesA well-designed monolith can have strong internal modules, clear interfaces, and independent teams working on different areas. Microservices become attractive when the organization requires independent deployment, differential scaling, technological autonomy, or clearer ownership of business capabilities. Typical candidates include order management, identity, payment, catalogue, notification, and shipment management. The central distinction is between a module and a service. A module is usually deployed within a larger process and shares runtime resources with other modules. A service is a separately running unit with an operational boundary. This boundary introduces benefits but also network latency, partial failure, versioning obligations, observability requirements, and data-consistency challenges. A service boundary should therefore be based on business cohesion rather than technical layers. A service that owns “all controllers” or “all database access” is usually a technical slice, not a business capability. Domain-driven design offers useful guidance: bounded contexts can provide candidate boundaries, while events, business language, ownership, and rates of change help refine them. Microservices architecture is often described through several principles: - services are organized around business capabilities;
- each service has clear ownership and a small, purposeful interface;
- services can be deployed and scaled independently;
- failures are expected and isolated where possible;
- automation supports testing, delivery, configuration, and infrastructure;
- operational telemetry is treated as part of the architecture.
These principles are goals rather than guarantees. A system may contain microservices yet remain tightly coupled if every change requires simultaneous deployment or if services share a database schema and internal classes. Core Concepts and TerminologyA service boundary defines what a service knows, owns, and changes. Service ownership includes behaviour, data stewardship, deployment, security, and operational responsibility. A bounded context is a domain boundary within which terms and models have consistent meanings. A synchronous call requires the caller to wait for a response. An asynchronous interaction, such as publishing an event, allows the producer and consumer to proceed more independently. Events should represent facts that have occurred, whereas commands request an action. The distinction is important for reliable integration and auditability. Service discovery is the process by which a service finds the network location of another service. In client-side discovery, the caller queries a registry and selects an instance. In server-side discovery, a router or load balancer performs that task. An API gateway is an entry point that receives external requests and routes them to internal services. A backend-for-frontend (BFF) is a client-specific backend designed for the needs of one interface, such as a mobile application or web portal. A sidecar is a companion process deployed with a service to provide supporting capabilities such as secure communication, telemetry, retries, or policy enforcement. A service mesh is a platform-level system that commonly uses sidecars or equivalent data-plane components to manage service-to-service traffic. An anti-corruption layer protects one model from another model’s concepts and assumptions. The strangler fig pattern gradually replaces an existing system by routing selected capabilities to new services. Branch-by-abstraction introduces a stable abstraction so that an implementation can be replaced incrementally without maintaining long-lived source-code branches. An architecture decision record (ADR) is a short document that records a significant architectural decision, its context, alternatives, consequences, and status. Quality attributes are measurable or assessable system properties such as availability, performance, security, modifiability, operability, and interoperability. Patterns for the Service Boundary and Communication EdgeAPI GatewayThe API gateway centralizes selected concerns at the system boundary. It may terminate TLS, authenticate requests, apply rate limits, route traffic, transform representations, aggregate responses, and provide a controlled public API. It can hide internal topology and prevent clients from becoming dependent on numerous internal endpoints. A gateway should not become a second monolith. Business rules that belong to a domain service should remain there. Excessive transformation logic, shared release cycles, or a single team bottleneck indicate that the gateway has accumulated too much responsibility. Multiple gateways or a gateway plus BFFs may be preferable when client needs differ substantially. Backend-for-FrontendA BFF gives each major client experience an API optimized for its workflow. A mobile BFF may minimize payload size and combine data needed for a single screen. A web BFF may support richer querying, while a partner BFF may enforce contractual and security requirements. The BFF pattern reduces client-side knowledge of internal services and allows independent evolution of client-specific representations. Its cost is additional code and the possibility of duplicated orchestration. The pattern is most valuable when channels have genuinely different data, interaction, performance, or security requirements. Service DiscoveryIn dynamic environments, service instances may be created, removed, or moved frequently. Hard-coded addresses are therefore unsuitable. A registry records service identity and location, often with health checks and expiration mechanisms. Discovery must account for stale records, failed instances, network partitions, and registration security. Service discovery does not solve communication reliability by itself. Calls still require timeouts, bounded retries, circuit breaking, idempotency, and suitable load balancing. A retry that repeats a non-idempotent payment operation can create serious business harm; resilience policies must reflect operation semantics. Sidecar and Service MeshA sidecar moves common infrastructure concerns out of application code. It can provide mutual TLS, traffic metrics, tracing propagation, policy checks, and controlled retries. A mesh can standardize these functions across many teams. The pattern also introduces operational complexity, resource consumption, configuration surfaces, and more difficult diagnosis. A sidecar cannot compensate for poor service boundaries or unclear business contracts. It should be adopted when the consistency and governance benefits justify its runtime and cognitive costs. Patterns for Modernizing Existing SystemsStrangler FigThe strangler fig pattern supports gradual replacement of a legacy system. A façade or routing layer directs selected functionality to new services while the remaining requests continue to reach the legacy application. Capabilities are migrated in slices, and the old path is removed only after the new path is reliable. Successful use requires a migration seam: an interface, route, event boundary, or data-access boundary through which behaviour can be redirected. Teams should define rollback routes, compare outputs where feasible, monitor traffic, and prevent the façade from becoming permanent accidental architecture. Anti-Corruption LayerWhen a legacy application uses inconsistent terminology or unsuitable data structures, directly exposing its model can contaminate the new service. An anti-corruption layer translates between models, protocols, identifiers, and error meanings. For example, a modern Order service may translate a legacy “customer account transaction” into an explicit order-payment interaction. The layer has a maintenance cost, but it preserves autonomy and prevents historical constraints from spreading. It should be treated as a deliberate boundary with tests and ownership rather than as undocumented adapter code. Branch-by-AbstractionBranch-by-abstraction is useful when a codebase must replace an implementation while keeping the main development line usable. Developers first introduce an abstraction, route existing calls through it, implement the new alternative, and switch behaviour through configuration or a feature flag. The old implementation is removed after validation. Unlike a long-lived feature branch, this approach exposes integration work early and reduces divergence. It requires disciplined interfaces, automated tests, observability, and careful removal of obsolete paths. Feature flags must have owners, expiry dates, and rollback procedures. Parallel Run and Strangler CombinationsFor high-risk decisions, old and new implementations may process the same input, with only one producing the official result. Outputs can be compared for a defined period. This is valuable for pricing, reporting, or eligibility calculations, but it doubles processing cost and requires deterministic comparison rules. It should not be used casually for operations with irreversible side effects unless side effects are safely simulated or isolated. Quality Attributes and Architectural Decision-MakingMicroservices choices should be evaluated against explicit quality attributes rather than fashion. A gateway may improve security centralization and client simplicity but reduce availability if deployed as a single bottleneck. A BFF may improve client performance but increase maintenance. A mesh may improve policy consistency but worsen operational simplicity. A practical evaluation begins by writing quality-attribute scenarios. For example: “When a catalogue instance fails during normal peak traffic, the system shall continue serving cached catalogue data within three seconds.” The scenario identifies stimulus, environment, response, and measurable response measure. Such statements are more useful than saying that the system must be “scalable.” Important considerations include: - Availability: Can failures be isolated, and are critical paths free from unnecessary dependencies?
- Performance: Are latency budgets allocated across gateway, network, services, and data stores?
- Modifiability: Can a service change without coordinated releases across the system?
- Security: Are identity, authorization, secrets, encryption, and audit responsibilities explicit?
- Operability: Can teams detect, diagnose, deploy, and recover from failures?
- Consistency: Which data must be immediately consistent, and where is eventual consistency acceptable?
- Cost: What are the infrastructure, staffing, observability, and cognitive costs?
An ADR should state the decision, context, considered alternatives, rationale, consequences, and status. A concise example is: Title: Use a BFF for the mobile ordering application Context: The mobile client requires a compact representation and must tolerate variable network conditions. Direct access to six internal services would expose topology and increase latency. Decision: Introduce a mobile BFF that aggregates catalogue, basket, and delivery estimates. Domain validation remains in the owning services. Consequences: The mobile experience can evolve independently and reduce round trips. The BFF becomes an additional deployable component and must have clear ownership, tracing, and contract tests. Alternatives rejected: Direct client-to-service access and one universal gateway response model. ADRs are not substitutes for design or testing. Their value lies in making reasoning visible, enabling informed review, and preserving organizational memory. Anti-Patterns and Failure ModesA distributed monolith consists of nominally separate services that must be released together, share a database schema, or make long synchronous call chains. It retains the complexity of distribution without gaining meaningful autonomy. Excessive service granularity creates “nano-services,” where deployment and communication overhead exceed the value of separation. The shared database anti-pattern allows many services to read and write one schema. Although convenient initially, it creates hidden coupling, unclear ownership, and migration conflicts. Shared infrastructure is not automatically wrong, but shared mutable business data should be approached cautiously and governed explicitly. Another failure is the synchronous call cascade: a request passes through gateway, BFF, service A, service B, and service C before a response is possible. Latency and failure probability accumulate. Alternatives include local read models, asynchronous events, response caching, or redesigning the workflow. Other warning signs include an oversized gateway, duplicated business rules, vague service ownership, absent contract tests, unbounded retries, inadequate tracing, and treating logs as the only observability mechanism. A service that cannot be deployed, monitored, or supported by an accountable team is not genuinely autonomous. Applied Case: Modernizing a Retail Order PlatformBackgroundThe illustrative organization, Northstar Retail, operates an online store built as a ten-year-old monolith. The system handles catalogue browsing, customer accounts, shopping baskets, orders, payments, and shipment notifications. Releases are infrequent because a change in one area requires regression testing of the whole application. Situation and ProblemMarketing requires rapid catalogue changes, while the fulfilment team needs independent changes to shipment workflows. Peak-season traffic causes the entire application to scale even when only catalogue traffic is high. The organization considers replacing the monolith with microservices but cannot accept a high-risk “big bang” rewrite. AnalysisArchitecture workshops identify catalogue and shipment as relatively cohesive capabilities with different rates of change. Payment is considered high risk because correctness, security, and auditability are more important than rapid extraction. The team also discovers that mobile clients call several undocumented endpoints directly, and the monolith’s customer and order models use different meanings for “account.” The principal quality goals are incremental delivery, availability of browsing, independent deployment for catalogue and shipment, traceability of orders, and protection of payment correctness. Immediate full data separation is judged too risky. Strategy and SolutionNorthstar first introduces an API gateway, authentication policy, request correlation identifiers, and centralized rate limiting. A mobile BFF aggregates catalogue and basket information, preventing the mobile application from depending on internal routes. The catalogue capability is extracted using the strangler fig pattern. Requests for new catalogue functions are routed to the Catalogue service, while un-migrated functions remain in the monolith. An anti-corruption layer translates legacy product classifications and identifiers. Catalogue updates are published as events, allowing a search index and read-oriented views to update asynchronously. For shipment management, the team creates an explicit Shipment service. The monolith publishes an order-confirmed event, and the Shipment service manages its own workflow. Idempotency keys and an inbox mechanism prevent duplicate event processing. The payment module is initially retained, protected behind a stable abstraction. A later replacement can use branch-by-abstraction and parallel comparison in a controlled environment. Service discovery is provided by the deployment platform, while timeouts, limited retries, circuit breaking, and distributed tracing are standardized. Each service has an owning team and an operational dashboard. ADRs record why catalogue was extracted before payment and why the BFF was introduced. OutcomeThe organization can deploy catalogue changes independently and scale catalogue workloads without scaling every function. Mobile clients make fewer network calls and are no longer coupled to internal service topology. Shipment processing becomes independently observable and resilient to temporary downstream delays. However, the platform now requires stronger operational skills, contract testing, event governance, and explicit handling of eventual consistency. Learning PointsThe case demonstrates that modernization is a sequence of controlled boundary decisions, not merely code decomposition. Extraction should follow business value and manageable risk. Translation layers protect new models, while gateways and BFFs should simplify external consumption without absorbing domain logic. Quality attributes and ADRs make trade-offs explicit and prevent architecture from being driven solely by technology enthusiasm. Governance, Security, and Professional ResponsibilityEvery service should have an accountable owner, documented API contracts, lifecycle status, dependency information, and defined support expectations. Governance should encourage useful standards—identity propagation, error formats, telemetry, secure defaults, and versioning—without imposing unnecessary central control over implementation details. Security responsibilities are distributed across boundaries. Gateways can authenticate and rate-limit, but services must authorize actions using service-appropriate rules. Sensitive data should be minimized, encrypted in transit and at rest where appropriate, and excluded from logs. Secrets must not be embedded in source code or configuration repositories. Audit records should support accountability without becoming a channel for unnecessary personal-data collection. Regulatory and contractual requirements may affect data residency, retention, deletion, financial records, and access logging. Eventual consistency is not an excuse to violate a legal obligation. Teams should identify which operations require strong guarantees, define recovery procedures, and test failure scenarios rather than assuming that infrastructure will prevent them. Current Directions and Best PracticePlatform engineering increasingly provides paved roads for service templates, deployment automation, policy enforcement, observability, and secure identity. Container orchestration and managed cloud services can reduce infrastructure effort, but they do not remove architectural responsibility. The growing use of OpenTelemetry-style traces, metrics, and logs improves cross-service diagnosis when teams also define meaningful correlation and service-level indicators. Event-driven integration, workload identity, policy-as-code, and progressive delivery are becoming common complements to microservices. AI-assisted coding may accelerate implementation, but generated code does not establish sound boundaries, reliable contracts, or appropriate security. Human review, automated tests, threat modelling, and ownership remain essential. Practical best practice is to begin with a modular design, extract only where independent change or scaling has clear value, keep APIs intentionally small, prefer explicit contracts, make operations observable, and delete transitional code after migration. Teams should measure deployment lead time, change failure rate, recovery time, latency, error rates, and service ownership health rather than counting services as a measure of success. Chapter SummaryMicroservices architecture organizes a system around independently deployable business capabilities, but distribution introduces network failure, data-consistency, operational, and governance costs. API gateways manage the external edge; BFFs tailor APIs to particular clients; service discovery supports dynamic locations; and sidecars or service meshes provide standardized infrastructure functions. Strangler fig, anti-corruption layer, branch-by-abstraction, and parallel-run patterns support gradual modernization. Effective architecture depends on quality-attribute scenarios, explicit trade-offs, accountable ownership, observability, secure contracts, and ADRs. Anti-patterns such as distributed monoliths, shared mutable databases, synchronous cascades, oversized gateways, and excessive service granularity undermine the intended benefits. The most reliable adoption strategy is incremental: establish seams, migrate valuable capabilities, measure outcomes, and remove transitional complexity. Key TermsService boundary: The conceptual and operational limit defining a service’s responsibilities, data, and interface. Bounded context: A domain boundary within which terminology and models have consistent meanings. API gateway: An entry point that routes and governs external requests to internal services. Backend-for-frontend: A client-specific backend optimized for a particular user interface or channel. Service discovery: The mechanism by which services locate available service instances. Sidecar: A companion process that supplies supporting capabilities for a service. Service mesh: Infrastructure for managing service-to-service communication and policy. Strangler fig pattern: Incremental replacement of a legacy system by routing capabilities to new services. Anti-corruption layer: A translation boundary that prevents one domain model from contaminating another. Branch-by-abstraction: Replacing an implementation through a stable abstraction rather than a long-lived branch. Parallel run: Operating old and new implementations together to compare results. Idempotency: The property that repeating an operation produces the same intended effect as performing it once. Circuit breaker: A resilience mechanism that temporarily stops calls to an unhealthy dependency. Distributed monolith: Services that are separate in deployment but tightly coupled in change and operation. Quality attribute: A measurable property such as security, availability, performance, or modifiability. Architecture decision record: A concise record of a significant architectural decision and its consequences. Eventual consistency: A model in which distributed replicas become consistent after a delay. Observability: The ability to understand internal system behaviour through telemetry. Contract test: A test verifying that an interaction conforms to an agreed service contract. Review QuestionsShort Answer Questions1. How does a service differ operationally from a module within a monolith? 2. What problem does an anti-corruption layer solve? 3. Why are timeouts and idempotency important when using service discovery? 4. State two appropriate responsibilities of an API gateway. 5. What information should an ADR normally contain? Long Answer Questions1. Explain the API gateway and backend-for-frontend patterns, including their benefits and risks. 2. Describe how the strangler fig pattern can be combined with an anti-corruption layer during modernization. 3. Discuss the principal quality attributes that should influence a microservices architecture decision. 4. Explain the causes and consequences of a distributed monolith. 5. Evaluate the operational benefits and costs of sidecars and service meshes. Analytical / Application Questions1. A mobile application directly calls eight internal services and suffers from high latency. Recommend an architecture using gateway and BFF principles, and identify possible risks. 2. A payment replacement must be introduced without interrupting order processing. Design a branch-by-abstraction and validation strategy, including rollback controls. 3. A proposed system has twelve services sharing one database and requiring simultaneous deployment. Diagnose the architectural problems and propose a staged improvement plan. 22. Multiple Choice Questions with AnswersQuestion 1Which condition most strongly indicates that two components should be separate microservices? A. They use different programming languages B. They represent cohesive business capabilities with different deployment or scaling needs C. They have different controller classes D. They are maintained in different folders Correct Answer: B. They represent cohesive business capabilities with different deployment or scaling needs Question 2What is the primary purpose of a backend-for-frontend pattern? A. To replace all domain services with one database B. To provide a client-specific API suited to a particular interface C. To eliminate authentication from internal services D. To force every client to use identical representations Correct Answer: B. To provide a client-specific API suited to a particular interface Question 3Which responsibility should generally remain in a domain service rather than being placed in an API gateway? A. TLS termination B. Request routing C. Business validation and domain rules D. Rate limiting Correct Answer: C. Business validation and domain rules Question 4What does the strangler fig pattern accomplish? A. It replaces a legacy system gradually through controlled routing of capabilities B. It combines all services into one deployment C. It prevents any coexistence between old and new implementations D. It removes the need for integration testing Correct Answer: A. It replaces a legacy system gradually through controlled routing of capabilities Question 5A new service must communicate with a legacy application whose terminology conflicts with the new domain model. Which pattern is most appropriate? A. Sidecar B. Anti-corruption layer C. Circuit breaker D. Service discovery Correct Answer: B. Anti-corruption layer Question 6Which situation is a distributed monolith? A. Services are independently deployable and own their data B. Services communicate asynchronously through stable events C. Services require coordinated releases and share hidden implementation coupling D. A gateway routes requests to multiple independent services Correct Answer: C. Services require coordinated releases and share hidden implementation coupling Question 7Why is an unbounded retry policy dangerous? A. It always improves latency B. It can amplify overload and repeat non-idempotent operations C. It prevents service discovery D. It removes the need for timeouts Correct Answer: B. It can amplify overload and repeat non-idempotent operations Question 8What is the central value of an architecture decision record? A. It replaces automated testing B. It records context, alternatives, rationale, and consequences of a decision C. It guarantees that an architecture will never change D. It documents only source-code syntax Correct Answer: B. It records context, alternatives, rationale, and consequences of a decision Question 9Which quality attribute is most directly improved by extracting a high-traffic catalogue service that can scale independently? A. Scalability B. Terminology consistency C. Source-code formatting D. Legal retention Correct Answer: A. Scalability Question 10What is the main purpose of branch-by-abstraction? A. To maintain a permanent parallel codebase B. To replace an implementation incrementally behind a stable abstraction C. To bypass integration with existing code D. To make every service share one model Correct Answer: B. To replace an implementation incrementally behind a stable abstraction
|