Microservices Architecture

Chapter 4
Architectural Design and Evolution

Bachelors Program in Full Stack Development
Microservices Architecture   •   Bachelor

Architectural Design and Evolution

Introduction

Microservices architecture is not simply the division of an application into small deployable programs. It is an approach to structuring software around business capabilities, autonomous teams, independently evolvable components, and explicit operational boundaries. The central design challenge is to obtain the benefits of modularity and independent delivery without creating an unmanageable distributed system.

Architectural design is therefore an evolutionary activity. Services, boundaries, technologies, and deployment arrangements must change as business requirements, team structures, traffic patterns, and regulatory obligations change. This chapter examines how to design service architectures, record architectural decisions, measure architectural fitness, and migrate safely from a monolith to microservices.

Learning Objectives

By the end of this chapter, you should be able to:

  • distinguish modularity, autonomy, and independence;
  • identify service boundaries using business capabilities and domain concepts;
  • apply evolutionary architecture and fitness functions;
  • create useful Architecture Decision Records (ADRs);
  • explain the strangler-fig pattern and anti-corruption layers;
  • plan a staged monolith-to-microservices transition;
  • evaluate the technical, organizational, financial, and governance consequences of decomposition.

1. Modularity and Service Boundaries

1.1 Modularity

Modularity is the division of a system into parts with high internal cohesion and limited coupling to other parts. A module should have a clear responsibility, a stable interface, and a degree of information hiding. In a microservices system, a service is a runtime and deployment unit, but the quality of its design depends on the same modular principles used in conventional software engineering.

Two properties are especially important:

  • Cohesion: the degree to which the elements of a module belong together.
  • Coupling: the degree of dependency between modules.

A useful design goal is high cohesion and low coupling. However, coupling cannot be eliminated. A business process such as placing an order necessarily involves customers, inventory, payment, and delivery. The objective is to make dependencies explicit, purposeful, and resilient rather than pretending that they do not exist.

1.2 Autonomy

A service is autonomous when its team can develop, test, deploy, and operate it with limited coordination from other teams. Autonomy normally requires:

  • ownership by a durable team;
  • a well-defined business responsibility;
  • an independently deployable artifact;
  • control over implementation details and data;
  • an explicit API or event contract;
  • adequate monitoring and operational responsibility.

Autonomy does not mean that a service has no dependencies. It means that dependencies do not routinely require synchronized releases or shared implementation changes.

A service that has its own process but must use another service’s database tables, deployment pipeline, and release schedule is not genuinely autonomous. It is a distributed module with operational overhead.

1.3 Choosing Boundaries

Service boundaries should generally follow business capabilities rather than technical layers. For example, an online retailer may have capabilities such as Catalog, Ordering, Payment, Inventory, and Shipping. A decomposition into “User Interface Service,” “Business Logic Service,” and “Database Service” often reproduces the layers of a monolith and creates services that change together.

Domain-driven design provides useful concepts for boundary discovery:

  • A bounded context is a boundary within which a domain model has a particular meaning.
  • A business capability is something the organization does to create value.
  • An aggregate is a consistency boundary within a domain model.
  • A domain event records that something meaningful has happened.

The same word can have different meanings in different contexts. “Customer” in a marketing context may contain preferences and campaign history, while “Customer” in billing may contain tax and payment information. Sharing one universal customer model can create coupling. Separate models connected through contracts may be safer.

Boundary candidates should be evaluated using several questions:

  1. What business outcome does this area support?
  2. Which data and rules change together?
  3. Which team has the knowledge and authority to own it?
  4. What consistency guarantees are truly required?
  5. How frequently does this area need to be deployed?
  6. What communication would be required across the proposed boundary?

A boundary is a hypothesis, not a permanent truth. Evidence from change patterns, incidents, delivery delays, and domain learning should cause boundaries to be revised.

2. Evolutionary Architecture

Evolutionary architecture treats architecture as a system that must remain capable of change. Instead of attempting to predict every future requirement, architects establish principles, constraints, and feedback mechanisms that support likely changes while preventing unacceptable degradation.

A useful evolutionary approach has four elements:

  1. Direction: define strategic qualities and architectural principles.
  2. Small changes: implement architecture incrementally rather than through a single irreversible transformation.
  3. Feedback: observe delivery, runtime, and business outcomes.
  4. Adaptation: revise designs when evidence invalidates earlier assumptions.

This approach does not imply the absence of upfront design. Some decisions—such as data ownership, security boundaries, regulatory controls, and failure-handling strategies—are expensive to reverse and deserve careful analysis. Other decisions, such as internal libraries or deployment details, may be intentionally deferred.

2.1 Fitness Functions

A fitness function is an automated or repeatable test that checks whether an architecture satisfies a desired characteristic. The term is associated with evolutionary architecture because it provides feedback as the system evolves.

Examples include:

  • verifying that services do not directly access another service’s database;
  • checking that all externally exposed endpoints require approved authentication;
  • measuring whether deployment frequency remains above an organizational target;
  • testing that an API remains backward compatible;
  • checking that a critical request meets a latency objective;
  • detecting dependencies on prohibited libraries or regions.

Fitness functions may be quantitative or qualitative. A quantitative function might measure the percentage of successful requests, while a structural function might fail a build when an architectural dependency rule is violated.

A fitness function should identify:

  • the quality attribute being protected;
  • the measurement method;
  • the threshold or rule;
  • the scope of the measurement;
  • the response when the threshold is breached.

For example, a reliability objective could be expressed as:

S = successfulrequests ÷ totalrequests × 100%

Here, S is the success percentage over a defined observation period. “Successful” must be defined—for example, HTTP status codes below 500—and the observation window, traffic population, and exclusions must be stated. This is not a universal reliability law; it is an operational measurement whose meaning depends on its definitions.

A latency fitness function might require the 95th percentile latency to remain below a specified service-level objective. Percentiles are preferable to averages when occasional slow requests matter, because an average can hide a long tail. A threshold should be selected from user needs and risk, not copied mechanically from another system.

Fitness functions are not substitutes for judgment. A team can meet latency targets while violating privacy, or achieve deployment frequency while increasing defects. Quality attributes must therefore be considered as a balanced set.

3. Architecture Decision Records

An Architecture Decision Record documents a significant architectural decision, its context, and its consequences. ADRs prevent organizations from repeatedly reopening settled questions and preserve the reasoning that would otherwise disappear when staff or circumstances change.

A practical ADR contains:

ElementPurpose
Title and statusIdentifies the decision and whether it is proposed, accepted, superseded, or rejected
ContextDescribes the forces, constraints, and problem
DecisionStates what will be done
AlternativesRecords options considered and why they were not selected
ConsequencesIdentifies benefits, costs, risks, and obligations
EvidenceLinks to experiments, measurements, standards, or incidents
Review triggerStates when the decision should be reconsidered

An ADR should describe a decision, not become a complete design document. For example:

Decision: The Order service will own order persistence; other services will access order information through an API or published events.

Context: Shared database access has caused schema coordination and release conflicts.

Alternatives: Continue shared access; create a shared data library; provide an API; publish events. An API and selected events were preferred because they preserve ownership while supporting different access needs.

Consequences: Teams must handle eventual consistency and contract evolution. Reporting workloads may require a separate read model.

A good ADR is honest about negative consequences. A document that lists only advantages is advocacy, not architectural reasoning. ADRs should also be revisited when assumptions change; an accepted decision is not an eternal rule.

4. Migration Patterns

4.1 The Strangler-Fig Pattern

The strangler-fig pattern enables gradual replacement of a legacy system. New functionality is implemented outside the monolith, while routing directs selected requests to the new component. Over time, the external components expand and the corresponding legacy functionality is retired.

The typical sequence is:

  1. establish observability and a reliable deployment process;
  2. identify a thin, valuable slice of functionality;
  3. place routing or a façade in front of the monolith;
  4. implement the slice in a new service;
  5. redirect suitable traffic;
  6. compare behavior and monitor outcomes;
  7. remove the replaced legacy path;
  8. repeat with the next slice.

The pattern reduces the risk of a “big bang” rewrite. It also creates a period in which the old and new systems coexist, so routing, data synchronization, failure handling, and operational ownership must be designed explicitly.

A migration slice should be valuable, bounded, observable, and capable of being separated from the monolith without creating excessive shared state. A technically easy but business-irrelevant extraction may produce little organizational value.

4.2 Anti-Corruption Layers

An anti-corruption layer (ACL) protects one model from the concepts and assumptions of another. It translates between models, protocols, data representations, and error semantics. The term comes from domain-driven design and is particularly useful when a new service must interact with a legacy system whose model is inconsistent or difficult to change.

An ACL may include:

  • request and response translators;
  • legacy identifier mapping;
  • validation and defaulting rules;
  • protocol adapters;
  • event translators;
  • error classification and retry policies.

For example, a legacy system may represent a shipment status as numeric codes, while a new Shipping service uses explicit values such as Prepared, Dispatched, and Delivered. The ACL translates between these models rather than forcing the new service to adopt the legacy representation.

An ACL has a cost: it introduces code, latency, and another place where defects may occur. It should therefore be deliberately owned, tested, monitored, and eventually reduced when the legacy dependency disappears. It should not become an unbounded integration “junk drawer.”

5. Monolith-to-Microservices Transition Strategies

A monolith is not automatically a failure. A well-modularized monolith may be easier to test, deploy, and operate than a poorly designed set of microservices. Migration should be justified by a concrete problem such as release bottlenecks, scaling asymmetry, organizational ownership, fault isolation, or regulatory separation.

5.1 Assessment Before Decomposition

Before extracting services, assess:

  • module dependency structure;
  • database tables and access patterns;
  • change frequency by module;
  • runtime load and scaling needs;
  • incident history;
  • team ownership and cognitive load;
  • security and compliance boundaries;
  • deployment and observability maturity.

A dependency map can reveal that two proposed services change together or share transactions. Such findings may indicate that the boundary is premature.

5.2 Common Transition Options

Modular monolith first: Strengthen internal boundaries while retaining one deployable unit. This is often the lowest-risk approach when the organization lacks testing, observability, or operational maturity.

Incremental extraction: Move selected capabilities into independently deployed services using the strangler pattern.

Parallel replacement: Build a new system beside the old one and migrate users or data in stages. This may be justified for severe legacy constraints but requires careful reconciliation.

Full rewrite: Replace the system in one major effort. This can be appropriate in rare circumstances, but it has high risk because users, requirements, and hidden business rules are discovered during implementation.

5.3 Data Migration and Ownership

Data is usually the hardest part of decomposition. A service should own the data required to enforce its business rules. Other services should not directly modify its tables. They may obtain information through APIs, events, or purpose-built read models.

During migration, several patterns are possible:

  • One-way replication: legacy changes are copied to a new store.
  • Dual writing: both systems are written, but inconsistent outcomes are possible and reconciliation is required.
  • Change data capture: database changes are published as events, subject to ordering and completeness constraints.
  • Read migration first: new consumers read from a new model before writes are transferred.
  • Write migration first: new service becomes authoritative while legacy reads are supported temporarily.

Dual writing is particularly risky because one write may succeed while the other fails. If it is unavoidable, use idempotency, durable outbox records, reconciliation jobs, and explicit ownership rules. The outbox pattern stores an event in the same local transaction as the business change, after which a publisher delivers it to a broker. This reduces—but does not eliminate—publication failure concerns.

5.4 Distributed Transactions and Consistency

A monolith may use a single database transaction across several modules. After decomposition, the same operation may span multiple services. Teams must decide whether the business process can tolerate eventual consistency.

A saga coordinates a business transaction as a sequence of local transactions. Each step has a compensating action if a later step fails. Compensation is not always a perfect reversal: refunding a payment, for example, may not restore every consequence of the original charge.

A choreography-based saga uses events and decentralized reactions. An orchestration-based saga uses a coordinator that directs the steps. Both require correlation identifiers, idempotent handlers, timeouts, retry policies, and operational visibility.

6. Applied Case: Illustrative Retail Migration

Background

This illustrative case concerns Northstar Retail, an online retailer with a seven-year-old Java monolith. The application contains catalog, order, payment, inventory, and shipping functions. A single release every three weeks is required because changes frequently touch shared tables and regression testing takes ten days.

Problem

The inventory function experiences seasonal load peaks, but scaling the whole application is expensive. The fulfillment team also needs to deploy shipping rules independently. Incidents show that direct database access across modules makes ownership unclear.

Analysis

The organization first maps business capabilities, code dependencies, database tables, and team responsibilities. It discovers that Shipping has relatively clear boundaries, while Order and Payment share transaction assumptions. Shipping is selected as the first extraction because it has a distinct team, a manageable interface, and a measurable operational benefit.

The team defines these initial fitness functions:

  • no new direct access to Shipping tables from outside the Shipping service;
  • at least 99.9% successful processing of accepted shipment commands over each calendar month;
  • backward-compatible API changes unless a versioned migration is approved;
  • complete correlation identifiers across order, shipping, and notification logs.

The thresholds are organizational targets, not guarantees of correctness. They must be reviewed against actual customer and regulatory needs.

Approach

A routing façade is placed before the monolith. The new Shipping service owns a new shipment store. An ACL translates the monolith’s delivery codes and address format into the Shipping domain model. During an initial period, shipment creation is mirrored to the new service in a controlled manner, and reconciliation compares records without allowing the shadow path to affect customers.

After confidence is established, selected orders route shipment operations to the new service. The service publishes ShipmentCreated and ShipmentDispatched events. Consumers use an event contract containing a version, event identifier, aggregate identifier, timestamp, and essential business data. Handlers are idempotent: receiving the same event twice does not create two shipments.

The team records decisions in ADRs, including why Shipping was selected first, why the ACL is temporary, and why the service uses eventual consistency for delivery notifications. Dashboards show command success, event lag, reconciliation differences, and legacy traffic.

Outcome

Shipping rules can now be released without a full monolith release, and seasonal scaling is limited to the relevant service. However, operational work increases: the team must manage event delivery, tracing, deployment, and data reconciliation. The migration is considered successful because it improves a defined constraint without claiming that every monolith function must become a service.

Learning Points

  1. Extraction should follow evidence and business value.
  2. A service boundary includes ownership and data authority, not merely a network endpoint.
  3. ACLs enable translation but must have a retirement strategy.
  4. Fitness functions convert architectural intentions into observable controls.
  5. Migration success should be evaluated through outcomes, not service count.

7. Governance, Security, and Professional Practice

Microservices enlarge the governance surface. Each service may expose APIs, process personal data, emit events, and require independent credentials. Security controls should include least-privilege access, secrets management, authentication, authorization, encryption in transit, secure logging, dependency scanning, and vulnerability response.

Data protection obligations require purpose limitation, retention controls, access auditing, and appropriate deletion or anonymization mechanisms. Event streams can complicate deletion because copies may exist in queues, caches, analytics systems, and backups. Architects must identify data propagation paths rather than treating the primary database as the only copy.

Teams should establish platform standards without forcing every service into identical implementation details. Useful standards include:

  • service templates with secure defaults;
  • common telemetry conventions;
  • documented API and event compatibility rules;
  • automated architectural tests;
  • incident response procedures;
  • ownership metadata and support contacts.

Standardization should reduce accidental complexity, while local autonomy should preserve appropriate technical choice. Excessive central control recreates a centralized architecture group as a delivery bottleneck; insufficient governance produces inconsistent security and operational practices.

8. Limitations and Failure Modes

Microservices can fail as an architectural strategy when organizations:

  • split services according to database tables rather than business capabilities;
  • create services too small for meaningful ownership;
  • share databases while claiming independent ownership;
  • distribute transactions without designing consistency behavior;
  • adopt containers and orchestration before establishing basic testing and monitoring;
  • measure success by the number of services;
  • use synchronous calls for every interaction, creating long dependency chains;
  • neglect platform costs, on-call load, and operational skill requirements.

A distributed system introduces network latency, partial failure, timeouts, retries, duplicate messages, version skew, and more difficult debugging. Retries can amplify overload if they are not bounded and combined with timeouts and circuit-breaking behavior. Asynchronous messaging reduces temporal coupling but introduces eventual consistency and more complex diagnosis.

The appropriate architecture may be a modular monolith, a few coarse-grained services, or a larger service ecosystem. The correct choice depends on business volatility, team structure, scale, risk, and organizational capability.

9. Contemporary Developments

Platform engineering increasingly provides self-service deployment, standardized observability, policy enforcement, and secure runtime environments. Internal developer platforms can make autonomous service ownership more realistic, but they should be evaluated by developer outcomes rather than platform feature count.

AI-assisted development can help generate adapters, tests, documentation, and migration scripts. It does not remove the need for domain analysis, security review, data governance, or architectural accountability. Generated code may reproduce legacy assumptions or introduce insecure behavior; human review and automated verification remain necessary.

Event-driven architectures, serverless runtimes, service meshes, and cloud-managed databases offer useful capabilities but also add abstraction and operational complexity. Each technology should be selected because it addresses a demonstrated constraint, not because it is associated with modern architecture.

Chapter Summary

Architectural design for microservices begins with modularity, clear business boundaries, team ownership, and data authority. Autonomy means that a team can evolve and operate a service without constant synchronized coordination, not that the service has no dependencies.

Evolutionary architecture uses incremental change and feedback. Fitness functions make important quality attributes and structural rules testable. ADRs preserve context, alternatives, consequences, and review triggers. During migration, the strangler-fig pattern enables gradual replacement, while anti-corruption layers protect new domain models from legacy concepts.

A monolith should be decomposed only when a concrete problem justifies the cost of distribution. Successful transitions address data ownership, consistency, observability, security, governance, and team capability. The quality of a microservices architecture is measured by business and operational outcomes—not by the number of services created.

Key Terms

Anti-corruption layer: A translation boundary that prevents one system’s model from contaminating another.

Architecture Decision Record: A concise record of an important architectural decision, its context, alternatives, and consequences.

Autonomy: The ability of a team and service to evolve, deploy, and operate with limited external coordination.

Bounded context: A boundary within which domain terms and models have consistent meaning.

Business capability: An organizational ability that creates or supports business value.

Choreography: Distributed coordination in which services react to events without a central coordinator.

Cohesion: The degree to which elements in a module support one responsibility.

Coupling: The degree of dependency between modules or services.

Eventual consistency: A condition in which replicas or services may temporarily differ but converge if updates are delivered.

Fitness function: An automated or repeatable test of an architectural quality or constraint.

Idempotency: The property that repeating an operation produces the same intended result as performing it once.

Modular monolith: A single deployable application organized internally around explicit modules.

Saga: A distributed business transaction composed of local transactions and compensating actions.

Service boundary: The conceptual and technical limit of a service’s responsibility, model, and data ownership.

Strangler-fig pattern: A gradual migration pattern in which new functionality surrounds and replaces legacy functionality.

Team cognitive load: The amount of knowledge and operational responsibility a team must manage effectively.

Transactional outbox: A pattern that stores a business change and its outgoing event in one local transaction before publication.

Vertical slice: A thin end-to-end capability crossing the layers needed to deliver business value.

Review Questions

Short Answer Questions

  1. What is the difference between cohesion and coupling?
  2. Why does autonomy require data ownership as well as independent deployment?
  3. What is the purpose of an Architecture Decision Record?
  4. How does an anti-corruption layer support migration?
  5. Why can a modular monolith be preferable to premature microservices?

Long Answer Questions

  1. Explain how bounded contexts and business capabilities can guide service boundaries. Include risks of sharing a universal domain model.
  2. Describe evolutionary architecture and explain how fitness functions provide architectural feedback.
  3. Compare the strangler-fig pattern with a full system rewrite.
  4. Discuss data ownership, eventual consistency, and sagas in a decomposed system.
  5. Explain the organizational and operational consequences of adopting microservices.

Analytical / Application Questions

  1. A payment and order module share one database transaction and are changed by the same team. Should they immediately become separate services? Analyze the evidence required before deciding.
  2. Design a staged migration for a library monolith in which loan processing, catalog search, and notifications have different scaling and release needs. Identify a first extraction and justify it.
  3. Propose three fitness functions for a regulated healthcare service architecture. Define each measurement, threshold or rule, and response when it fails.

Multiple Choice Questions with Answers

Question 1

Which characteristic most directly supports a service team’s ability to release independently?

A. Shared database writes by all services

B. Independent deployment and clear ownership

C. A single release train for the whole system

D. Identical domain models in every service

Correct Answer: B. Independent deployment and clear ownership

Question 2

What is the primary purpose of a fitness function?

A. To replace all architectural judgment

B. To measure or test an architectural quality or constraint

C. To guarantee that a service never fails

D. To determine the number of services required

Correct Answer: B. To measure or test an architectural quality or constraint

Question 3

Which item is normally part of an Architecture Decision Record?

A. Only source-code listings

B. The decision’s context, alternatives, and consequences

C. A guarantee that the decision will never change

D. Only the final deployment script

Correct Answer: B. The decision’s context, alternatives, and consequences

Question 4

What does the strangler-fig pattern emphasize?

A. Replacing the entire legacy system in one release

B. Gradually routing functionality to new components and retiring old paths

C. Sharing all legacy tables with new services

D. Avoiding observability during migration

Correct Answer: B. Gradually routing functionality to new components and retiring old paths

Question 5

What is the role of an anti-corruption layer?

A. To force every service to use the legacy model

B. To translate between models and protect a new model from legacy assumptions

C. To eliminate all network communication

D. To combine every service database

Correct Answer: B. To translate between models and protect a new model from legacy assumptions

Question 6

Which statement about a modular monolith is correct?

A. It cannot have explicit module boundaries

B. It may provide strong modularity without distributed-system overhead

C. It always scales each capability independently

D. It necessarily has a shared, uncontrolled data model

Correct Answer: B. It may provide strong modularity without distributed-system overhead

Question 7

Why is unrestricted direct access to another service’s database problematic?

A. It improves data ownership

B. It creates coupling and undermines independent evolution

C. It guarantees backward compatibility

D. It eliminates schema coordination

Correct Answer: B. It creates coupling and undermines independent evolution

Question 8

In a saga, what is used when a later local transaction fails?

A. A compensating action, where one is possible

B. A universal distributed rollback guarantee

C. An automatic deletion of all historical data

D. A requirement to restart every service

Correct Answer: A. A compensating action, where one is possible

Question 9

Which migration practice is especially risky because two writes can diverge?

A. Using a versioned API

B. Dual writing without reconciliation controls

C. Recording an ADR

D. Adding correlation identifiers

Correct Answer: B. Dual writing without reconciliation controls

Question 10

What should primarily determine whether an organization adopts microservices?

A. The number of available frameworks

B. A concrete business or operational problem and the organization’s capability

C. The desire to maximize service count

D. Whether containers are fashionable

Correct Answer: B. A concrete business or operational problem and the organization’s capability

Microservices Architecture
Chapter 4 of 14