Foundations of Serverless Applications and Microservices
Chapter 3
Microservices Architecture and Service Decomposition
Diploma in Cloud Computing
Foundations of Serverless Applications and Microservices
•
Diploma
Microservices Architecture and Service Decomposition
Introduction
Microservices architecture is an approach to building software as a collection of small, independently deployable services. Each service implements a coherent business capability, owns its operational data, and communicates with other services through explicit interfaces. Unlike a monolithic application, in which most functionality is deployed as one unit, a microservices system allows teams to develop, test, release, scale, and govern services with a significant degree of independence.
Microservices are not simply small applications connected by a network. Their value depends on sound decomposition. A poorly decomposed system may contain many services but still have strong coupling, duplicated logic, difficult transactions, and fragile deployments. The central design problem is therefore deciding where service boundaries should be placed.
This chapter develops that problem using domain-driven design (DDD), bounded contexts, cohesion, loose coupling, service ownership, and the database-per-service principle. It also considers practical trade-offs, governance, observability, security, and the role of serverless platforms in deploying microservices.
Learning Objectives
By the end of this chapter, you should be able to:
explain the characteristics and trade-offs of microservices architecture;
distinguish a domain, subdomain, bounded context, and service boundary;
use domain-driven design to identify candidate services;
evaluate service boundaries using cohesion and coupling;
apply independent deployability and service ownership principles;
explain the database-per-service principle and its consequences;
design communication and consistency patterns for distributed services;
identify operational, security, governance, and organizational risks; and
propose a defensible decomposition for a practical business system.
1. Microservices as an Architectural Style
A microservice is a separately deployable software component that normally:
represents a meaningful business capability;
owns a focused set of responsibilities;
exposes a stable contract, such as an HTTP API, event interface, or message endpoint;
controls changes to its internal implementation and data; and
can be operated independently, at least to a useful degree.
A microservices architecture generally contains several services, an API gateway or edge layer where appropriate, messaging infrastructure, databases or other data stores, and operational capabilities such as logging, monitoring, tracing, and automated deployment.
The architectural goals are independence, resilience, evolvability, and alignment between software structure and business structure. These goals are not free. Network calls introduce latency and failure modes; data consistency becomes harder; testing requires distributed-system techniques; and operations become more demanding.
A monolith is not inherently inferior. A well-structured monolith may be the best choice for a small team, an early product, or a domain with low scale and limited operational capacity. Microservices become attractive when independent change, scaling, team autonomy, fault isolation, or organizational growth justify the added complexity.
A service is not merely a technical layer
A service should not normally be defined as “the database service,” “the validation service,” or “the controller service.” Such divisions reproduce technical layers across the network and create excessive coordination. A business-oriented service might instead be “Order Management,” “Payment,” “Inventory,” or “Customer Identity.” Technical concerns such as authentication, logging, and configuration remain important, but they do not automatically define business service boundaries.
Comparison of a monolithic application and independently deployable microservices
Figure: Comparison of a monolithic application and independently deployable microservices
Figure: A monolithic deployment compared with independently deployable business services.
2. Domain-Driven Design and Bounded Contexts
Domain-driven design, associated especially with Eric Evans, treats the business domain as the primary source of software structure. The domain is the area of knowledge and activity for which the system is being built, such as retail, banking, logistics, or healthcare.
A large domain is usually divided into subdomains:
Core subdomain: a capability that provides strategic differentiation and deserves the greatest design attention.
Supporting subdomain: necessary for the business but not usually a major source of competitive advantage.
Generic subdomain: a common capability that may be purchased, reused, or implemented using a standard solution.
A bounded context is a boundary within which a particular domain model and vocabulary are consistent. The same word can have different meanings in different contexts. For example, “customer” in Marketing may mean a person targeted by a campaign, while “customer” in Billing may mean an account responsible for payment. Treating both meanings as one universal object often creates ambiguity and coupling.
A bounded context is a conceptual boundary, not automatically a deployable service. In many systems, however, a bounded context is a strong candidate for a service because it provides a coherent model, vocabulary, responsibility, and ownership boundary.
Ubiquitous language
Within a bounded context, domain experts and developers should use a shared vocabulary called the ubiquitous language. Terms, rules, and events should be made explicit. If one team says “order confirmed” to mean payment authorized, while another uses it to mean goods dispatched, an apparently simple integration may conceal a serious semantic error.
Context mapping
Context mapping describes relationships between bounded contexts. Common relationships include:
Customer–supplier: one context depends on another, and the supplier provides an agreed interface.
Conformist: a downstream context adopts the upstream model because it has little influence over it.
Anti-corruption layer: translation protects one model from another model’s concepts or data structures.
Shared kernel: contexts share a small, jointly governed model; this should be used cautiously because it increases coordination.
Published language: communication uses a documented, stable representation such as a well-defined event schema.
These relationships are useful because service integration is not just a technical connection; it is a relationship between models and teams.
3. Discovering Candidate Service Boundaries
Service decomposition should begin with business capabilities and domain behavior rather than existing code files or database tables. A practical discovery process is as follows.
Step 1: Identify business outcomes
Ask what the organization must accomplish: accept an order, authorize payment, reserve stock, deliver a parcel, or issue a refund. Outcomes are more useful than screens because several screens may participate in one capability.
Step 2: Identify domain events and commands
A command requests a change, such as PlaceOrder or CancelPayment. An event records that something has happened, such as OrderPlaced or PaymentAuthorized. Event-storming workshops can expose workflows, policies, actors, and points where language or ownership changes.
Step 3: Group related behavior
Operations that change together, depend on the same rules, and use the same vocabulary are candidates for one boundary. This is a cohesion argument. For example, calculating an order total, applying order-level discounts, and changing order status may belong together.
Step 4: Identify invariants
An invariant is a condition that must remain true. For example, an order may not be confirmed unless required payment authorization exists. If several data elements must be updated atomically to preserve an invariant, placing them in one service may be appropriate. Splitting them forces a distributed consistency mechanism.
Step 5: Examine change and scaling patterns
If one capability changes frequently while another is stable, separation may allow independent release. If inventory receives much higher traffic than customer profiles, independent scaling may be valuable. These are supporting considerations, not automatic reasons to split.
Step 6: Define ownership and contracts
Each candidate service needs an accountable team, an API or event contract, security responsibilities, data ownership, and operational indicators. If no team can own a service, the boundary is probably premature or artificial.
Step 7: Test the boundary
Ask whether the service can be deployed without deploying its consumers, whether its data can be changed without editing another service’s tables, and whether its interface expresses business concepts rather than internal implementation details.
4. Cohesion and Loose Coupling
Cohesion measures how strongly the responsibilities within a module or service belong together. A highly cohesive service has a focused purpose and a small, understandable conceptual model. Coupling measures how strongly one component depends on another. Low coupling means that internal changes do not routinely require coordinated changes elsewhere.
These are design qualities rather than precise universal numerical measures. A useful practical test is the change scenario:
If a business rule changes, how many services must change?
If a database column changes, how many consumers break?
If a service is unavailable, which business functions stop?
Does a request require a long synchronous chain of services?
A boundary is suspicious when unrelated capabilities are always deployed together, or when a service has many consumers that depend on its internal data model. Another warning sign is a “distributed monolith”: several deployable units that cannot actually be released or operated independently.
Coupling through data
Direct access to another service’s database is one of the strongest forms of coupling. It bypasses the owner’s rules, exposes internal schema, and makes independent evolution difficult. Even read-only access can become a hidden contract. Data should generally be requested through an API or delivered through an explicitly designed event or replicated read model.
Coupling through synchronous calls
Synchronous calls are appropriate when an immediate response is necessary and the dependency is reliable. They become dangerous when a user request requires a long chain such as A → B → C → D. Latency and failure accumulate, and a temporary failure in one component may prevent the entire operation. Asynchronous messaging can reduce temporal coupling, although it introduces eventual consistency and message-management responsibilities.
5. Independent Deployability and Service Ownership
Independent deployability means that a service can be built, tested, released, and rolled back without requiring all related services to be released simultaneously. It does not mean that services have no dependencies. Rather, dependencies are managed through compatible contracts, versioning, backward compatibility, and automated tests.
A service team should normally own:
business behavior and implementation;
API and event contracts;
persistent data and migrations;
automated tests and deployment pipeline;
reliability, security, and operational monitoring; and
documentation and incident response.
The “you build it, you run it” principle connects development responsibility with operational responsibility. Ownership reduces ambiguity during incidents and encourages teams to design services that are supportable in production.
Independent deployment requires technical practices such as contract testing, backward-compatible schema evolution, feature flags, canary or rolling releases, automated rollback, and clear dependency policies. A service that requires manual coordination with five other teams for every release is not meaningfully independent.
6. The Database-per-Service Principle
The database-per-service principle states that each service owns its persistent data and that other services do not directly read or write its database. The implementation may be a separate database instance, schema, or datastore, provided that ownership and access boundaries are genuinely enforced.
The principle provides:
encapsulation of data and business rules;
freedom to choose an appropriate storage technology;
independent schema evolution;
reduced accidental coupling; and
clearer accountability for data quality and security.
It also creates costs. Data may be duplicated, cross-service queries become harder, transactions may span services, and reporting may require an analytical store or data pipeline.
Distributed transactions and eventual consistency
A local database transaction can preserve invariants within one service. Across services, a single atomic transaction is usually undesirable or unavailable. Instead, workflows may use a saga: a sequence of local transactions coordinated by events or a workflow component. If a later step fails, compensating actions attempt to reverse or mitigate earlier effects.
For example:
Order Service records an order as pending.
Payment Service authorizes payment and emits PaymentAuthorized.
Inventory Service reserves stock and emits StockReserved.
Order Service changes the order to confirmed.
If stock is unavailable, Payment Service receives a cancellation request and the order becomes rejected.
This process is eventually consistent: different services may temporarily show different states. The design must define acceptable intermediate states, retries, timeouts, duplicate-message handling, and reconciliation procedures.
An event should represent a meaningful fact and include a stable identifier, occurrence time, event type, and sufficient data for consumers. Consumers should be idempotent where possible, meaning that processing the same message more than once does not create an incorrect additional effect.
7. Communication and Interface Design
Services commonly communicate through:
REST or HTTP APIs for request–response interactions;
gRPC or similar remote procedure mechanisms for efficient internal calls; and
message brokers or event streams for asynchronous communication.
An API contract should specify inputs, outputs, errors, authentication requirements, rate limits, and compatibility expectations. It should avoid exposing internal database structures. A good contract expresses business intent, for example POST /orders/{id}/cancellation, rather than requiring consumers to manipulate an internal status column.
Events should be designed for evolution. Additive changes are generally safer than renaming or removing fields. Consumers should tolerate unknown fields and producers should document semantic changes. Schema registries, versioned contracts, and consumer-driven contract tests can support this discipline.
Retries require care. Retrying a read is usually safer than retrying a non-idempotent command. Commands should use idempotency keys or unique request identifiers when duplicate submission is possible. Timeouts and circuit breakers prevent a failing dependency from consuming unlimited resources.
The following is a fictional illustrative case designed to demonstrate decomposition decisions.
Background
Northstar Retail operates an online store. Its initial application is a monolith containing customer accounts, product catalogues, shopping carts, orders, payments, inventory, and shipping. Releases are slow because a change to any area requires testing and deploying the whole application. During seasonal promotions, inventory traffic increases sharply and affects unrelated functions.
Problem analysis
A preliminary analysis identifies several domain models:
Catalog: products, descriptions, prices, and availability information for browsing.
Ordering: carts, order lines, totals, order status, and cancellation rules.
Payment: authorization, capture, refunds, and payment-provider interaction.
Inventory: stock levels, reservations, release of reservations, and warehouse rules.
Fulfilment: shipment creation, tracking, and delivery status.
Identity: authentication, customer credentials, and account security.
Although “product availability” appears in both Catalog and Inventory, the concepts differ. Catalog needs a customer-facing availability representation; Inventory owns the authoritative stock and reservation rules. Catalog may consume inventory events and maintain a read model rather than querying Inventory’s database.
Proposed approach
Northstar creates an Order Service, Payment Service, Inventory Service, Fulfilment Service, Catalog Service, and Identity Service. Each service has a separate repository or clearly isolated codebase, a responsible team, an API contract, and an owned datastore.
The checkout workflow is asynchronous after the initial order request. Order Service records a pending order and publishes OrderPlaced. Payment Service attempts authorization. Inventory Service reserves stock. When both successful events are received, Order Service confirms the order and Fulfilment Service creates a shipment request.
The system uses an idempotency key for checkout requests. Payment and Inventory consumers record processed event identifiers to avoid repeating authorization or reservation. A reconciliation process finds orders that remain pending beyond an agreed time threshold. Operational dashboards display authorization failures, reservation failures, event age, and order completion rates.
Outcome
Catalog traffic can scale independently during browsing peaks. Payment-provider changes remain within Payment Service. Inventory rules can evolve without exposing warehouse tables to Order Service. However, customer support must be trained to understand eventual consistency, and the organization must fund message monitoring, tracing, contract testing, and incident response.
Learning points
The decomposition succeeds because it follows business capabilities, separates differing models, assigns ownership, and accepts explicit workflow complexity. It would fail if services shared tables, required synchronized releases, or merely divided the old monolith by technical layers.
9. Serverless Microservices
Serverless platforms can host microservices using functions, managed HTTP endpoints, queues, event buses, and managed databases. They are useful when workloads are variable, event-driven, or operational teams want to reduce infrastructure administration.
A serverless function is not automatically a microservice. A function may be one implementation unit inside a service. A service usually includes its business rules, contracts, data ownership, deployment configuration, observability, and security policy. Excessive function-level decomposition can create a “distributed spaghetti” system with many network boundaries and difficult debugging.
Serverless design introduces concerns such as cold-start latency, execution limits, concurrency controls, provider-specific services, permission granularity, and cost variability. Database-per-service remains relevant, although the datastore may be a managed relational database, document store, key-value store, or event-oriented system. Teams should choose technology according to access patterns and consistency requirements rather than novelty.
10. Security, Governance, and Compliance
Every service should authenticate callers and authorize actions according to least privilege. Service identities should have only the permissions required for their responsibilities. Sensitive data should be encrypted in transit and at rest, and secrets should be held in managed secret stores rather than source code or unencrypted configuration.
Distributed systems increase the number of trust boundaries and communication paths. Logging must avoid exposing passwords, payment credentials, health information, or unnecessary personal data. Data retention, deletion, access requests, and auditability may be required by privacy and sector regulations. A service boundary must not become an excuse to duplicate sensitive data without a defined purpose and retention policy.
Governance should be lightweight but explicit. Recommended controls include API ownership, threat modelling, dependency inventories, schema review, vulnerability scanning, incident procedures, and service-level objectives. Central governance should establish guardrails; teams should retain enough autonomy to make local implementation decisions.
11. Challenges and Limitations
Microservices introduce several recurring difficulties:
Operational complexity: more deployments, logs, metrics, alerts, and configurations.
Network unreliability: calls can time out, fail, or return partial results.
Data consistency: business processes may be temporarily inconsistent.
Testing complexity: integration, contract, resilience, and end-to-end tests are needed.
Version management: contracts and events must evolve safely.
Organizational coordination: service boundaries must align with capable teams.
Cost: infrastructure, observability, messaging, and operational expertise may cost more than a monolith.
Boundary erosion: urgent shortcuts can create shared databases and undocumented dependencies.
These limitations support a gradual approach. A modular monolith can establish domain boundaries before deployment separation. A service should be extracted when there is a clear benefit, a stable boundary, and sufficient operational readiness.
12. Best-Practice Checklist
Before creating or extracting a service, ask:
What business capability does it own?
Is its vocabulary internally consistent?
Which invariants must it enforce?
Can its data be owned without direct database access by others?
Can it be deployed and rolled back independently?
Which team is accountable for it in production?
Are synchronous dependencies necessary, or could events be used?
How will retries, duplicates, timeouts, and failures be handled?
How will contracts evolve?
What security, privacy, observability, and compliance controls apply?
A service boundary is a hypothesis. Production evidence, incidents, change patterns, and team experience should be used to refine it.
Chapter Summary
Microservices architecture organizes software around independently deployable business capabilities. Its success depends less on the number of services than on the quality of their boundaries. Domain-driven design helps identify subdomains, bounded contexts, shared vocabulary, and relationships between models. Cohesion keeps related behavior together, while loose coupling limits the impact of change.
Independent deployability requires explicit contracts, automation, compatibility practices, and clear service ownership. The database-per-service principle protects data ownership and enables independent evolution, but it introduces distributed transactions, eventual consistency, duplication, and reporting challenges. APIs, events, sagas, idempotency, retries, observability, and reconciliation are practical responses to these challenges.
Microservices and serverless platforms can complement one another, but neither technology removes the need for sound domain modelling. Security, privacy, governance, and operational capability must be designed into every service. When the benefits do not justify distributed-system complexity, a modular monolith may be the more responsible choice.
Key Terms
Domain: The business or problem area addressed by a software system.
Subdomain: A distinct area of responsibility within a broader domain.
Core subdomain: A strategically differentiating capability.
Supporting subdomain: A necessary capability that is not the main competitive differentiator.
Generic subdomain: A common capability suitable for a standard or purchased solution.
Bounded context: A boundary within which a domain model and vocabulary are consistent.
Ubiquitous language: A shared vocabulary used by domain experts and the development team.
Context map: A description of relationships between bounded contexts.
Microservice: A focused, independently deployable software component representing a business capability.
Cohesion: The degree to which responsibilities within a component belong together.
Coupling: The degree of dependency between components.
Service ownership: Accountability for a service’s behavior, data, deployment, security, and operation.
Independent deployability: The ability to release a service without coordinated deployment of all related services.
Database-per-service: A principle in which each service owns and controls its persistent data.
Eventual consistency: A state in which distributed replicas or services may temporarily differ before converging.
Saga: A sequence of local transactions coordinated across services, often with compensating actions.
Idempotency: The property that repeated processing produces the same intended result as one processing attempt.
Anti-corruption layer: A translation boundary that protects one domain model from another.
Distributed monolith: Technically separate components that remain tightly coupled operationally or contractually.
Review Questions
Short Answer Questions
What distinguishes a bounded context from a general domain?
Why is direct access to another service’s database discouraged?
Define cohesion and coupling in the context of service design.
What is the purpose of an idempotency key?
Why might a modular monolith be preferable to microservices?
Long Answer Questions
Explain how domain-driven design can guide the decomposition of an online retail system.
Discuss the benefits and costs of the database-per-service principle.
Explain how independent deployability depends on contracts, automation, and service ownership.
Compare synchronous APIs and asynchronous events for communication between services.
Describe the role of sagas and compensating actions in a distributed checkout workflow.
Analytical / Application Questions
A university system contains admissions, fee payment, course registration, examinations, and student identity in one application. Propose candidate bounded contexts and justify your boundaries using business capabilities and invariants.
A proposed architecture has eight services, all reading a shared customer database and requiring simultaneous releases. Diagnose the design problems and recommend a staged improvement plan.
Design a failure-handling sequence for an order in which payment succeeds but inventory reservation fails. Identify events, compensating actions, data ownership, and the information required for reconciliation.
Multiple Choice Questions with Answers
Question 1
Which characteristic most strongly indicates that a component is a microservice?
A. It contains exactly one database table
B. It represents a focused business capability and can be deployed independently
C. It is written in a different programming language from every other component
D. It contains only user-interface code
Correct Answer: B. It represents a focused business capability and can be deployed independently
Question 2
What is the primary purpose of a bounded context?
A. To define a network firewall rule
B. To ensure that one database engine is used everywhere
C. To establish a boundary for a consistent domain model and vocabulary
D. To eliminate the need for business analysis
Correct Answer: C. To establish a boundary for a consistent domain model and vocabulary
Question 3
Which practice most directly violates the database-per-service principle?
A. Publishing a versioned business event
B. Using an API to request another service’s data
C. Maintaining a read model from subscribed events
D. Directly updating another service’s database tables
Correct Answer: D. Directly updating another service’s database tables
Question 4
What is a major benefit of high cohesion in a service?
A. Unrelated responsibilities are grouped together
B. The service has a focused purpose and its behavior is easier to change
C. All services must use the same datastore
D. Network communication is eliminated
Correct Answer: B. The service has a focused purpose and its behavior is easier to change
Question 5
Which situation is an example of eventual consistency?
A. Two fields are updated atomically in one local transaction
B. A service rejects an invalid request immediately
C. An inventory view temporarily differs from the latest reservation state before receiving an event
D. A compiler reports a syntax error
Correct Answer: C. An inventory view temporarily differs from the latest reservation state before receiving an event
Question 6
What is the main role of an anti-corruption layer?
A. To encrypt every database backup automatically
B. To translate and protect one domain model from another model
C. To combine all bounded contexts into one model
D. To prevent all asynchronous communication
Correct Answer: B. To translate and protect one domain model from another model
Question 7
A checkout request may be submitted twice because of a network retry. Which design technique best prevents two identical charges?
A. Removing all timeout settings
B. Using an idempotency key processed by the payment service
C. Allowing every consumer to write to the payment database
D. Replacing the API with a user interface
Correct Answer: B. Using an idempotency key processed by the payment service
Question 8
Which observation is the strongest warning sign of a distributed monolith?
A. Services use documented APIs
B. A service has an accountable owning team
C. Services require coordinated releases and share internal database tables
D. Services emit business events
Correct Answer: C. Services require coordinated releases and share internal database tables
Question 9
In a saga, what is a compensating action?
A. A database index created for performance
B. An action that attempts to mitigate the effect of an earlier completed step
C. A replacement for authentication
D. A method of removing all service boundaries
Correct Answer: B. An action that attempts to mitigate the effect of an earlier completed step
Question 10
Which factor is most important when deciding whether to split a monolith into a separate service?
A. Whether the service name sounds technically modern
B. Whether the boundary represents a coherent capability with a clear owner and justified independence
C. Whether the service contains fewer than ten classes
D. Whether every service uses a different cloud provider
Correct Answer: B. Whether the boundary represents a coherent capability with a clear owner and justified independence
Foundations of Serverless Applications and Microservices
Chapter 3
of 15