Vicedomini Softworks

Software Architecture

Architects: DDD for Microservices Checklist, Bubble Contexts and ACLs

27 September 2026

Architects: DDD for Microservices Checklist, Bubble Contexts and ACLs

Use DDD, specifically bounded contexts and tactical patterns, to find service boundaries and avoid both god services and anemic CRUD microservices. Domain analysis grounds boundaries in business capability rather than technical layering. Inside a service, aggregates and domain events preserve consistency, while across services, sagas and integration events coordinate distributed transactions. Full DDD earns its cost only where domain complexity justifies it, leaving simpler subdomains to run as CRUD services.


TL;DR:

  • Bounded contexts should be identified based on distinct terminology, independent lifecycles, and separate ownership, with clear context maps documenting their relationships.
  • Inside a service, small aggregates with referenced identities improve scalability, while domain and integration events should be distinct and published only after local transactions commit.
  • API and event schemas must follow the ubiquitous language to ensure consistency, with contracts stabilized through consumer-driven testing and versioning strategies.
  • Full DDD is justified for subdomains with complex rules, states, or compliance needs, whereas CRUD services suit simple, low-change data like reference tables.
  • Legacy migration benefits from bubble contexts and anti-corruption layers, with a cautious, incremental approach that decouples functionality before shifting traffic.

Vicedomini Softworks
vicedominisoftworks.com
Build Boundaries That Scale
Vicedomini Softworks helps organizations design custom software, modernize legacy systems, and integrate complex ecosystems through direct engineer collaboration.
Explore our engineering services

Table of Contents

How to identify bounded contexts and convert them to service boundaries

A bounded context is the boundary within which a particular domain model and its vocabulary remain consistent and unambiguous. When the same term carries different meanings in different parts of an organisation, that divergence signals a natural seam. Martin Fowler frames this directly: bounded contexts let teams maintain separate models and separate languages for different parts of a large system, with explicit context maps documenting how those models relate. A word such as “customer” might mean a billing record in finance and a support ticket owner in service operations. That gap is not an inconsistency to fix. It is evidence that two distinct models, and likely two distinct services, already exist.

Architects looking for split candidates should watch for a specific set of signals:

  • Distinct terminology or business rules for what appears to be the same entity across teams.
  • Independent lifecycles, where one concept changes far more often, or on a different release cadence, than another.
  • Separate ownership, staffing or service-level agreements governing different parts of the domain.

Once candidate contexts emerge, context mapping clarifies how they should interact. Relationships come in recognisable shapes: upstream and downstream dependencies, a conformist relationship where a downstream team simply adopts the upstream model, a shared kernel where two teams deliberately share a slice of the domain, and an anti-corruption layer that translates between models without letting either leak into the other. Documenting these relationships early, according to domain analysis guidance from Microsoft Learn, keeps the eventual service boundaries aligned with business capabilities rather than accidental technical convenience.

Key tactical DDD patterns for service internals: aggregates, repositories and domain events

Once a bounded context is drawn, tactical patterns govern what happens inside it. An aggregate is the transactional consistency boundary: the smallest cluster of objects that must be saved or changed together to keep business invariants true. Tactical DDD guidance from Microsoft Learn states that a microservice should be no smaller than a single aggregate and no larger than a bounded context, and that aggregates should reference other aggregates by identity rather than by object reference, relying on eventual consistency across them. Keeping aggregates small matters for scalability: a large aggregate locks more state per transaction and becomes a contention point under load.

Repositories mediate persistence for aggregates and should expose collection-like operations while hiding storage mechanics entirely. Domain classes behind that repository stay persistence-ignorant, modelled as plain objects, which the .NET microservices architecture guidance describes as POCO or POJO classes free of infrastructure concerns. This separation keeps the domain layer testable and independent of any particular database or framework.

Domain events and integration events serve different audiences and should never be conflated. A domain event fires inside the boundary to trigger side effects within the same service; an integration event, published only after the originating transaction commits, informs other services that something has happened.

A practical sequence for modelling an aggregate and publishing its events:

  1. Identify the invariant that must always hold true and draw the aggregate boundary around exactly what enforces it.
  2. Designate a single aggregate root as the only entry point for external interaction.
  3. Commit the local transaction first, then publish the resulting domain event.
  4. Translate that domain event into an integration event before it crosses the service boundary.
  5. Let downstream services subscribe and react asynchronously, never synchronously blocking the publisher.

Pro Tip: Publish integration events only after the local commit succeeds; publishing before commit risks notifying other services about a change that might still roll back.

Using ubiquitous language to design APIs and event contracts

The vocabulary agreed inside a bounded context should carry straight through to its API resources and event names. When an aggregate is called an “Order” internally, the resource path, the event name and the payload fields should all say “Order” too, never a mix of domain and database terminology. Payloads should express business intent rather than mirror table columns: an “OrderShipped” event describes what happened in the domain, not which rows changed.

Contract stability matters as much as naming. A few practices keep contracts durable as services evolve:

  • Apply consumer-driven contract testing so a producing service knows immediately when a proposed change would break a consumer.
  • Version contracts lightly, favouring additive changes over breaking ones wherever the domain allows it.
  • Specify REST resources with the OpenAPI Specification, which gives producers and consumers a shared, machine-readable contract to validate against.
  • Keep event schemas as disciplined as API schemas, since an undocumented event contract causes the same coupling problems as an undocumented endpoint.

Treating ubiquitous language as the source of both API and event vocabulary reduces the translation errors that occur when a team names things one way in code, another in documentation and a third in conversation with stakeholders.

Decision criteria: when to invest in DDD and when CRUD is better

Not every subdomain deserves the same modelling investment. Martin Fowler’s writing on microservices stresses organising services around business capability and evolving decomposition gradually, rather than applying maximum rigour everywhere by default.

Subdomains that justify full DDD treatment tend to share these traits:

  • Rich business rules and invariants that must hold even under concurrent, high-volume access.
  • Complex lifecycles, where an entity moves through many states with rules governing each transition.
  • Regulatory, audit or visibility requirements that demand a precise, explicit model of what happened and why.

Subdomains suited to plain CRUD services usually show the opposite pattern:

  • Reference data or lookup tables with little or no business logic attached.
  • Simple create-read-update-delete flows where the “domain rule” is essentially the database schema.
  • Low change frequency and low consequence if a record is edited directly.

The two approaches coexist comfortably in one architecture. A rich order-management context can sit alongside a plain CRUD service for shipping-carrier reference data, integrating through a well-defined API rather than a shared database. The discipline lies in resisting the temptation to model every subdomain as though it carried the same complexity as the most critical one.

Migration tactics: bubble contexts, anti-corruption layers and incremental decomposition

Bubble context and translation layer illustration

Migrating domain logic out of a legacy monolith carries real risk if the new model is exposed directly to legacy assumptions. Domain Language’s guidance on legacy systems recommends building a bubble context: a small, protected space where a clean new model can be developed, shielded by an anti-corruption layer that translates between the new design and the legacy system’s concepts. That guidance describes three ACL flavours: an umbilical ACL used while the bubble still depends heavily on the legacy system, a synchronising ACL that keeps two models aligned as both evolve, and an autonomous ACL for a bubble that has effectively become independent.

A safe migration sequence typically follows this order:

  1. Establish the bubble context and its anti-corruption layer before writing any new domain logic.
  2. Decouple one capability at a time, translating legacy calls through the ACL rather than rewriting the legacy system wholesale.
  3. Redirect traffic for that capability to the new service once its behaviour matches the legacy path under load.
  4. Retire the legacy code path only after monitoring confirms the new service is stable in production.

Martin Fowler’s guidance on breaking apart a monolith warns against a common pitfall: producing many small CRUD services that simply mirror database tables, which multiplies operational overhead without delivering the modularity microservices are meant to provide. Starting with larger, behaviour-rich services and splitting further only once a team can operate and monitor each one independently avoids that trap.

Pro Tip: Before splitting a service further, confirm you can trace a request end-to-end across the current boundaries using existing logging and tracing; if you cannot, fix observability before you fix granularity.

Vicedomini Softworks perspective: an engineering-first checklist

Validating a bounded context before committing engineering time to it follows a compact sequence: domain discovery with the business owners who actually use the vocabulary, an event-storming session to surface the verbs and state changes that matter, a context map showing how the resulting contexts relate, a shortlist of candidate aggregates, a working prototype of the APIs and events those aggregates expose, and a final set of operational readiness gates covering monitoring, tracing and rollback before anything ships.

At Vicedomini Softworks, this sequence runs faster because clients work directly with the engineers doing the modelling, not through an account-management layer relaying decisions back and forth. That direct line shortens the feedback loop between a proposed boundary and a validated one, which matters most in the early stages when a wrong aggregate boundary is cheap to fix and an expensive one to discover in production. Readers designing or auditing service boundaries can review our architecture and consultancy services for a sense of how that discovery process is structured.

— Pepe F.

Authoritative resources to consult next

For deeper technical detail, Microsoft Learn’s domain analysis and tactical DDD pages cover boundary discovery and aggregate design respectively. Martin Fowler’s bounded context writing remains the reference for strategic modelling, and the saga pattern page covers distributed transaction coordination. For legacy integration, consult Domain Language’s guidance on bubble contexts.

Reaching your next architecture milestone

Bounded contexts and aggregates give you the map, but drawing that map under deadline pressure, while a legacy system keeps running, is where most teams lose time. Vicedomini Softworks works as an extension of your engineering team rather than as an outside reviewer: you talk directly to the engineers modelling your domain, not to an account manager translating between you and them, which tends to catch a wrong aggregate boundary before it becomes a production incident.

For a focused architectural spike, our Initial assessment, priced from €3,500 as a one-off engagement, produces a written context map and boundary recommendation you can act on immediately. Teams that need ongoing architectural oversight through a migration can engage our CTO Advisory plan, from €1,800 per month, scaling up to Fractional CTO from €4,000 per month or CTO Partner from €7,500 per month for deeper, sustained involvement. You can also review our full range of custom software and architecture services to see how a code audit or hands-on development engagement fits alongside the advisory work. Get in touch to scope your next assessment.

Sources

FAQ

What is the difference between DDD and microservices?

Domain-driven design is a modelling discipline for finding and structuring business boundaries, while microservices is an architectural style for deploying independent services. Microsoft Learn’s domain analysis guidance treats DDD as the method for discovering where those service boundaries should sit, rather than a competing approach.

What is a bounded context in DDD?

A bounded context is the boundary within which a specific domain model and its vocabulary stay consistent, even if the same term means something different elsewhere in the organisation. Martin Fowler’s definition ties this to explicit context maps that document how separate models relate to one another.

How do sagas maintain consistency across microservices?

Sagas coordinate a business transaction that spans multiple services by chaining local transactions together and running compensating actions if a later step fails. The saga pattern is a pragmatic alternative to distributed ACID transactions, implemented through either choreography with events or explicit orchestration.

When should I use CRUD instead of full DDD modelling?

CRUD services fit subdomains with reference data, simple lookups or minimal business logic, where the effort of full domain modelling would outweigh the benefit. Complex subdomains with rich invariants, intricate lifecycles or audit requirements are better served by DDD’s aggregates and domain events.

How do anti-corruption layers help legacy migration?

An anti-corruption layer translates between a new domain model and a legacy system, protecting the new model from contamination by outdated concepts. Domain Language’s guidance describes umbilical, synchronising and autonomous ACL variants depending on how independent the new bubble context has become.