API Design and Microservices Architecture

Chapter 3
Domain Modeling and Service Decomposition

Post Graduate Diploma in Full Stack Development
API Design and Microservices Architecture   •   Post Graduate

3. Domain Modeling and Service Decomposition

Introduction and Context

Microservices architecture is often presented as a technical strategy for dividing a large application into independently deployable services. That description is incomplete. The difficult question is not how to create several applications, but where the boundaries between them should be placed. Poorly chosen boundaries produce services that are tightly coupled, frequently deployed together, and dependent on one another’s databases. Such systems may be distributed physically while remaining monolithic in design. They are commonly described as distributed monoliths.

Domain modeling provides a way to make service boundaries meaningful. It begins with the business problem rather than with frameworks, database tables, or organizational preferences. Domain-driven design (DDD), event storming, bounded contexts, aggregates, business capabilities, and context mapping offer complementary tools for understanding a domain and translating that understanding into APIs and deployable services.

This chapter examines how a development team can move from business language to domain models, from domain models to API resources, and from API resources to service boundaries. The emphasis is on judgment. There is no universal service size or decomposition algorithm. A good design reflects business cohesion, data ownership, team responsibilities, operational requirements, and the cost of distributed communication.

Learning Objectives

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

1. Explain the purpose of domain-driven design in API and microservices architecture.

2. Distinguish domains, subdomains, bounded contexts, aggregates, entities, and value objects.

3. Use event storming and business-capability analysis to discover candidate boundaries.

4. Apply context mapping to identify relationships and integration responsibilities.

5. Translate domain concepts into API resources and commands without exposing internal models indiscriminately.

6. Evaluate service boundaries using cohesion, coupling, data ownership, change patterns, and operational considerations.

7. Recognize distributed monoliths, excessive fragmentation, and other decomposition failures.

8. Make defensible architectural decisions in a professional case scenario.

From Business Domain to Software Boundary

A domain is the area of knowledge and activity that an organization exists to serve. In an online retailer, the domain may include product discovery, ordering, payment, inventory, delivery, and customer support. The domain is broader than the software system; it includes policies, roles, terminology, events, and processes that may be partly manual.

A subdomain is a meaningful portion of the wider domain. DDD commonly distinguishes core, supporting, and generic subdomains. The core subdomain provides competitive differentiation and deserves the greatest modeling attention. Supporting subdomains are necessary but not usually the principal source of competitive advantage. Generic subdomains, such as authentication or basic email delivery, may be purchased or implemented using standard solutions.

This classification influences architecture. A core subdomain may justify a carefully designed service and a specialized model. A generic subdomain may be better implemented through a reliable external platform. Treating every subdomain as equally strategic often leads to unnecessary complexity.

A bounded context is an explicit boundary within which a domain model has consistent meaning. The same term can legitimately mean different things in different contexts. For example, a “customer” in a marketing context may have preferences, segments, and campaign consent, while a “customer” in a payment context may be represented primarily by billing identity and payment risk information. Attempting to create one universal Customer object can create ambiguity and excessive coupling.

Bounded contexts are conceptual boundaries first and deployment boundaries second. A bounded context may initially be implemented as a module in a modular monolith. It can later become a separately deployed service if independent deployment, scaling, ownership, or fault isolation justify that move. This distinction is important: DDD helps discover coherent boundaries, but it does not require immediate distribution.

A Vocabulary for Modeling

An entity is an object defined by continuity and identity rather than only by its current attributes. An Order remains the same order even if its shipping address changes. An entity normally has an identifier and a lifecycle.

A value object is defined by its attributes and has no independent identity. Money, Address, EmailAddress, and DateRange are common examples. Value objects are useful because they express domain meaning and can enforce rules such as valid currency or postal-code formats. They should generally be immutable.

An aggregate is a cluster of related objects treated as a consistency boundary. It has one aggregate root through which external code accesses the cluster. For example, an Order may be the root of order lines, pricing information, and order status. External clients should not directly modify an order line in a way that bypasses order rules.

The aggregate is not simply a database join or a convenient object graph. Its purpose is to define which invariants must be maintained together. If an order cannot be confirmed without at least one line item, that rule belongs within the Order aggregate. If inventory reservation involves a large stock network and independent timing, it may belong to a separate Inventory aggregate and context, coordinated through a process rather than one transaction.

An invariant is a condition that must remain true. Examples include “a payment cannot be captured twice,” “a shipment cannot contain an unfulfilled order,” and “a reservation cannot exceed available stock.” Identifying invariants is central to deciding what belongs inside an aggregate and what can be eventually consistent.

Commands express requests to change state, such as SubmitOrder, ReserveStock, or CancelSubscription. Events describe facts that have occurred, such as OrderSubmitted or StockReserved. Queries retrieve information without changing domain state. Separating these concepts clarifies API intent and helps teams decide where behavior belongs.

Discovering the Model Through Event Storming

Event storming is a collaborative modeling technique in which domain experts and technical participants explore a business process through domain events. Participants typically begin with events expressed in the past tense: OrderPlaced, PaymentAuthorized, DeliveryDispatched. They then identify commands that cause events, actors who issue commands, policies that react to events, and aggregates that handle commands.

A useful event-storming sequence is:

1. Establish the business scope and invite people who understand actual operations, not only software implementation.

2. Capture significant events in chronological or causal order.

3. Identify uncertainty, disagreement, and events that cross departmental or contextual boundaries.

4. Add commands, actors, policies, and candidate aggregates.

5. Group concepts according to language, business responsibility, and consistency requirements.

6. Mark external systems and integration points.

7. Convert the resulting model into candidate bounded contexts and process flows.

The value of event storming lies less in the final diagram than in the conversations it provokes. A finance specialist may use “settled” to mean a different state from the one used by an order specialist. Such differences reveal potential boundaries. Teams should record unresolved terms rather than concealing them behind a common but vague data model.

Event storming should not be treated as a mechanical method for turning every event into a microservice. Many events belong to one context, and some are merely implementation details. The technique discovers business meaning; architectural judgment is still required.

Business Capabilities as a Second Lens

A business capability describes what an organization can do, such as manage catalog, price products, accept payment, fulfill orders, or manage identity. Capabilities are relatively stable compared with organizational processes, which may change frequently.

Capability mapping is particularly useful when process diagrams are too detailed or when teams organize themselves around technical layers. A candidate service should normally own a coherent capability rather than a technical function such as “database access” or “validation.” A Product Catalog service may own product descriptions and publishing rules. A Pricing service may own price calculation and promotional policies. A User Interface service does not represent a business capability and is therefore a poor basis for domain decomposition.

Capability analysis and event storming complement each other. Event storming shows how work and state changes flow through the domain; capability mapping shows which organizational responsibility sustains that work. When both indicate a boundary, confidence in the candidate context increases.

Context Mapping and Relationships Between Contexts

A context map describes relationships between bounded contexts. It makes dependencies explicit instead of allowing them to emerge accidentally through shared tables or undocumented API calls.

A downstream context consumes information or services from an upstream context. For example, an Order context may depend on a Product Catalog context for product information. A conformist relationship occurs when the downstream team adopts the upstream model because it lacks the power or incentive to translate it. An anti-corruption layer (ACL) protects one context from another context’s model by translating concepts, formats, and semantics at the boundary.

A published language is a shared, documented exchange model used for integration. Open-host service describes an upstream context that provides a well-defined protocol for multiple consumers. A partnership relationship reflects coordinated evolution between teams. A shared kernel is a deliberately shared subset of the model, but it carries risk: changes require coordination and can recreate coupling.

Context mapping is not merely a diagramming exercise. It should answer practical questions: Which context owns a fact? Who translates data? What happens when the upstream system is unavailable? Is the integration synchronous or asynchronous? Which team absorbs breaking-change risk? What consistency guarantee is required?

Aggregates, Transactions, and Consistency

A common decomposition mistake is designing aggregates around database tables. A better approach begins with invariants and transaction boundaries. Objects that must change atomically should usually be within one aggregate, provided the aggregate remains reasonably small and focused. Objects that can change independently should not be placed together solely for convenience.

Across service boundaries, distributed transactions are expensive and fragile. Many microservice systems therefore use local transactions plus events and compensating actions. Suppose an order is accepted before inventory is confirmed. The order context may create an order in a Pending state and publish OrderSubmitted. The inventory context attempts reservation and publishes StockReserved or StockUnavailable. A process manager or saga then advances the order or initiates cancellation.

Eventual consistency is not an excuse to ignore correctness. The business must define acceptable intermediate states, time limits, retry behavior, idempotency, and compensation. A customer-facing API should communicate whether an operation is complete, pending, or rejected. “Success” must not falsely imply that all downstream work has completed.

Translating Models into API Resources

An API resource should represent a meaningful contract for consumers, not necessarily an internal entity or database table. RESTful resources often correspond to domain entities, but actions and workflows may require command-oriented endpoints. For example:

  • `POST /orders` may create an order draft.
  • `POST /orders/{id}/submission` may express the business command to submit it.
  • `GET /orders/{id}` may return an order representation appropriate to the consumer.
  • `POST /orders/{id}/cancellation` may request cancellation subject to policy.

The API should expose behavior through terms recognized by the bounded context. If “confirm” is a meaningful business action, a generic `PUT` that permits arbitrary status changes may be too weak a contract. Conversely, not every method in a domain model deserves an endpoint.

Resource representations should be designed for the consumer’s needs while preserving ownership boundaries. An Order API may include a product name captured at purchase time, but it should not allow clients to modify catalog descriptions through the order resource. Cross-context data can be represented as a snapshot, reference, or composed view, depending on freshness and ownership requirements.

API design must also reflect lifecycle and failure semantics. Idempotency keys are important for commands such as payment or order submission, where network retries can otherwise produce duplicate effects. Versioning, error codes, correlation identifiers, pagination, authorization, and clear state transitions are part of the domain contract, not merely infrastructure details.

Choosing Service Boundaries

A candidate service boundary should be evaluated against several questions:

  • Does it own a coherent business capability?
  • Is its language internally consistent and distinct from neighboring contexts?
  • Does it own the data required to enforce its invariants?
  • Can it be changed, tested, and deployed with limited coordination?
  • Does it have a clear team responsible for its operation and evolution?
  • Are its performance, availability, and security requirements different from those of neighboring capabilities?
  • Can communication across the boundary tolerate latency, failure, and eventual consistency?

The strongest boundaries usually have high cohesion and low coupling. Cohesion means that the responsibilities within the service belong together. Coupling means that the service depends on other components for change, deployment, runtime availability, or shared data. A boundary that creates many synchronous calls for every business operation may be technically separate but architecturally weak.

Service size should be judged by responsibility and change patterns rather than lines of code. A service that is too large may conceal unrelated models and release coordination. A service that is too small may create chatty communication, duplicated infrastructure, operational overhead, and complicated debugging. A modular monolith is often the better starting point when domain boundaries are uncertain or the organization lacks operational maturity.

Recognizing Distributed Monoliths and Excessive Fragmentation

A distributed monolith has separately deployed components that remain tightly coupled. Symptoms include:

  • several services must be deployed together for one feature;
  • services directly access one another’s databases;
  • a single user request requires a long chain of synchronous calls;
  • one service cannot function or test without many others;
  • shared libraries contain business rules rather than technical utilities;
  • teams cannot identify clear ownership of data or decisions.

Excessive fragmentation is the opposite failure. It occurs when trivial capabilities become independent services without sufficient business or operational justification. A service for every table, endpoint, or noun can create a network of remote object calls. The result is greater latency and failure exposure without meaningful autonomy.

The remedy is not always to merge services immediately. Teams should identify the coupling mechanism, establish data ownership, replace direct database access with explicit contracts, reduce synchronous chains, and consider consolidating contexts that share invariants and release cadence. Architecture should evolve through measured refactoring rather than ideological commitment to a particular number of services.

An Illustrative Case: Northstar Commerce

Background

Northstar Commerce is a fictional mid-sized retailer whose original application is a modular monolith. It sells household products through web and mobile channels. The company plans to support multiple warehouses, external payment providers, and independent release of the customer experience.

Situation and Problem

A proposed architecture divides the system into Customer, Product, Cart, Order, Payment, Inventory, Shipping, Notification, and Reporting services. The proposal appears modular, but the initial design allows the Order service to read Product and Inventory databases directly. Checkout performs synchronous calls to Cart, Product, Pricing, Payment, Inventory, Shipping, and Notification. Every change to an order requires a shared `OrderStatus` library used by five teams.

The architecture team must determine whether the proposed boundaries are sound and define a safer initial design.

Analysis

An event-storming workshop identifies these events: CartCheckedOut, OrderSubmitted, PaymentAuthorized, StockReserved, OrderConfirmed, ShipmentCreated, and CustomerNotified. Participants discover that “product” has different meanings. Catalog owns descriptions and images; pricing owns selling rules; inventory owns stock positions; an order needs a historical item description and price rather than a live catalog object.

Capability analysis identifies catalog management, pricing, order management, payment processing, inventory management, fulfillment, and customer communication. The workshop also shows that notification is a supporting capability, not a core decision-making context. Reporting is primarily a read and analytical concern and should not own transactional business facts.

The proposed synchronous checkout chain crosses too many failure boundaries. Payment authorization and stock reservation require explicit business policies, but notification does not need to block order confirmation. Direct database access violates ownership and prevents independent evolution.

Approach and Solution

Northstar first creates a modular monolith with explicit modules for Catalog, Pricing, Ordering, Payment, Inventory, and Fulfillment. Each module owns its model and communicates through application interfaces and domain events. This reduces immediate operational complexity while preserving boundaries.

The Order module owns the Order aggregate and its lifecycle. It stores snapshots of item name and price at submission time. Payment and Inventory own their own aggregates and publish outcomes. A process manager coordinates the workflow:

1. Ordering publishes OrderSubmitted.

2. Payment attempts authorization and publishes PaymentAuthorized or PaymentDeclined.

3. Inventory attempts reservation and publishes StockReserved or StockUnavailable.

4. Ordering confirms the order only when the required conditions are satisfied.

5. Fulfillment creates a shipment after confirmation.

6. Notification reacts asynchronously and does not determine transactional success.

An anti-corruption layer translates catalog and pricing representations into the ordering model. The reporting model consumes events into a separate read store rather than querying transactional databases. Idempotency keys protect payment and submission commands, while correlation identifiers allow the complete workflow to be traced.

After operational monitoring and team ownership mature, Payment and Inventory are extracted as services because they have distinct security, scaling, and operational requirements. Ordering and Fulfillment remain together temporarily because they share a rapidly changing workflow and require frequent coordinated changes.

Outcome

Northstar avoids a premature collection of small services while gaining explicit ownership and asynchronous workflow boundaries. The system accepts eventual consistency where appropriate, communicates pending states to clients, and prevents notification failures from cancelling an otherwise valid order. Later extraction is guided by observed change and operational needs rather than by the original service list.

Learning Points

The case demonstrates that service decomposition is an evolutionary design activity. Business capabilities, event flows, invariants, and team responsibilities must be considered together. A bounded context can exist before it becomes a network service, and asynchronous events are useful only when their semantics, failure handling, and ownership are clear.

Professional and Governance Considerations

Domain boundaries often correspond to organizational authority. If no team owns a capability, its service is likely to become neglected or controlled through informal coordination. Conversely, aligning architecture too rigidly with the current organization can preserve temporary reporting structures. Architects should distinguish stable business responsibility from accidental team arrangement.

Data governance is equally important. A service that owns personal, payment, or regulated information must define access rules, retention, auditability, and deletion behavior. An API composition layer should not expose sensitive data merely because it can retrieve it. Privacy principles such as data minimization and purpose limitation should influence resource design.

Event contracts require governance. Events should have documented meaning, schema evolution rules, compatibility expectations, ownership, and retention policies. Consumers should not infer business facts from field names without a defined contract. Observability should include logs, metrics, traces, event delivery status, and dead-letter handling, while avoiding unnecessary exposure of personal information.

Technology, Automation, and Emerging Practice

Modern platforms make service deployment easier, but containers, service meshes, and orchestration platforms do not create good boundaries. They can automate deployment, discovery, traffic management, retries, and observability; they cannot determine whether two capabilities share an invariant or whether a business term is ambiguous.

Contract testing can verify that API and event consumers remain compatible without requiring every service to be deployed together. Schema registries, event catalogs, architectural decision records, and automated dependency analysis support governance. Distributed tracing helps identify chatty calls and hidden coupling. Analytics on deployment frequency, change-failure rate, latency, and incident concentration can provide evidence for boundary refinement.

AI-assisted modeling can summarize workshop transcripts, cluster terms, suggest event candidates, or detect repeated dependencies. Such tools should support, not replace, domain experts. Generated models may reproduce organizational bias, confuse correlation with ownership, or expose confidential information. Human validation, access control, and traceability of architectural decisions remain necessary.

Best Practices for Decomposition

Begin with business language and capabilities, not databases or technology stacks. Identify invariants and transaction boundaries before selecting aggregates. Use event storming to expose different meanings and process dependencies. Maintain explicit ownership of data and decisions. Prefer contracts and translation layers over shared databases and shared domain models.

Start with the simplest deployment model that preserves the intended boundaries. Extract a service when independent deployment, scaling, security, resilience, or ownership provides a measurable benefit. Use asynchronous communication for workflows that can tolerate eventual consistency, but specify retries, idempotency, timeouts, compensation, and user-visible states. Review boundaries using operational evidence after the system is running.

Chapter Summary

Domain modeling provides the conceptual foundation for responsible service decomposition. Domains contain subdomains, while bounded contexts establish consistent meanings and ownership. Entities, value objects, aggregates, commands, events, and invariants help define models and transaction boundaries. Event storming reveals business events and disagreements; capability mapping identifies stable organizational responsibilities; context mapping makes relationships and translation responsibilities explicit.

Good API resources express domain concepts and supported behavior without exposing internal structures indiscriminately. Services should own coherent capabilities, data, and invariants, and should be evaluated through cohesion, coupling, change patterns, team ownership, and operational requirements. Distributed monoliths result from hidden coupling, shared databases, synchronous chains, and coordinated releases. Excessive fragmentation results from turning every technical object into a service. A modular monolith may be an appropriate intermediate or long-term architecture. Decomposition is evolutionary, evidence-based, and inseparable from governance, observability, security, and organizational responsibility.

Key Terms

Domain: The business area, knowledge, and activities addressed by a system.

Subdomain: A distinct area of responsibility within a broader domain.

Core subdomain: A strategically differentiating part of the business domain.

Bounded context: A boundary within which a domain model and its language have consistent meaning.

Entity: A domain object identified by continuity and identity.

Value object: An immutable concept defined by its attributes rather than identity.

Aggregate: A consistency boundary containing a root and related domain objects.

Aggregate root: The entity through which an aggregate is accessed and modified.

Invariant: A rule that must remain true within a defined consistency boundary.

Command: An explicit request to perform a state-changing operation.

Domain event: A significant fact about something that has occurred in the domain.

Business capability: An organizational ability to perform a meaningful business function.

Context map: A representation of relationships and integration patterns between bounded contexts.

Anti-corruption layer: A translation mechanism that protects one model from another model’s concepts and formats.

Event storming: A collaborative workshop technique for discovering domain events, commands, policies, and boundaries.

Saga: A distributed business transaction coordinated through local transactions and events.

Eventual consistency: A model in which related data becomes consistent after asynchronous processing rather than immediately.

Distributed monolith: A set of deployed components that remain tightly coupled in operation or change.

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

Modular monolith: A single deployable application with explicit, internally separated modules and ownership boundaries.

Review Questions

Short Answer Questions

1. What is a bounded context, and why can the same domain term have different meanings in different contexts?

2. Distinguish an entity from a value object using one example of each.

3. What purpose does an aggregate root serve?

4. How does event storming support service-boundary discovery?

5. Define a distributed monolith and identify one symptom of it.

Long Answer Questions

1. Explain how domain-driven design connects subdomains, bounded contexts, aggregates, and API design.

2. Describe the process of using event storming and business-capability mapping together to identify candidate services.

3. Discuss why distributed transactions are difficult and how events, sagas, compensation, and idempotency can address the problem.

4. Compare a modular monolith with a microservices architecture as alternative ways of implementing bounded contexts.

5. Explain context mapping and discuss the role of an anti-corruption layer in protecting domain models.

Analytical / Application Questions

1. An application has separate Customer, Address, and Email services, but every checkout requires synchronous calls to all three and all teams share one release schedule. Analyze whether the decomposition is justified and propose a better boundary strategy.

2. A banking system uses the term “account” in deposits, lending, and customer support. Apply bounded-context reasoning to explain how these meanings might be modeled and integrated without creating one universal Account model.

3. Design an event-driven order workflow involving payment and inventory. Identify the principal commands, events, consistency decisions, failure cases, and API states that a client should observe.

22. Multiple Choice Questions with Answers

Question 1

Which characteristic most strongly indicates that two concepts belong in the same aggregate?

A. They are stored in the same database table.

B. They must change together to preserve a business invariant.

C. They are both nouns in the requirements document.

D. They are accessed by the same user interface.

Correct Answer: B. They must change together to preserve a business invariant.

Question 2

What is the primary purpose of a bounded context?

A. To ensure every business term has one universal definition.

B. To separate development teams by programming language.

C. To establish a boundary within which a model and its language are consistent.

D. To require each context to become an independently deployed service.

Correct Answer: C. To establish a boundary within which a model and its language are consistent.

Question 3

During event storming, “PaymentAuthorized” is best classified initially as what?

A. A domain event

B. A value object

C. A database constraint

D. A user interface resource

Correct Answer: A. A domain event

Question 4

Which design best protects an Order context from changes in a Product Catalog model?

A. Allowing Order to read the Catalog database directly

B. Using a shared mutable Product class in both contexts

C. Creating an anti-corruption layer that translates catalog data

D. Duplicating the entire Catalog application inside Order

Correct Answer: C. Creating an anti-corruption layer that translates catalog data

Question 5

A service that cannot be deployed unless four other services are deployed simultaneously most likely exhibits which problem?

A. High cohesion

B. A distributed monolith

C. A value-object model

D. A published language

Correct Answer: B. A distributed monolith

Question 6

Why might an order store a product name and price as a snapshot at submission time?

A. To allow the order to modify the catalog later

B. To preserve the historical meaning of the purchased item

C. To eliminate the need for product ownership

D. To ensure all contexts share one database record

Correct Answer: B. To preserve the historical meaning of the purchased item

Question 7

Which situation most strongly justifies extracting a bounded context into a separate service?

A. It contains one database table.

B. Its class names are different from another module’s class names.

C. It has distinct scaling, security, ownership, or deployment requirements.

D. The team wants to increase the number of deployments.

Correct Answer: C. It has distinct scaling, security, ownership, or deployment requirements.

Question 8

In an order workflow, why should notification normally be asynchronous?

A. Notification must own the Order aggregate.

B. A notification failure need not invalidate a successfully confirmed order.

C. Asynchronous communication eliminates all failure handling.

D. Notifications are always generic subdomains.

Correct Answer: B. A notification failure need not invalidate a successfully confirmed order.

Question 9

Which statement best describes eventual consistency?

A. All services update one shared database transactionally.

B. Data becomes consistent after asynchronous processing and may temporarily differ.

C. No business invariants are enforced.

D. Every API request is processed twice for reliability.

Correct Answer: B. Data becomes consistent after asynchronous processing and may temporarily differ.

Question 10

What is the principal risk of creating one microservice for every domain noun or database table?

A. The system may become excessively fragmented and communication may become chatty.

B. The system will necessarily lose all data persistence.

C. The services will automatically become bounded contexts.

D. The architecture will prevent independent deployment.

Correct Answer: A. The system may become excessively fragmented and communication may become chatty.

API Design and Microservices Architecture
Chapter 3 of 15