ALD in Domain-Driven Design (DDD)
DDD helps you discover the right domain boundaries and language. Architect-Led Development (ALD) helps you express those boundaries as role-based contracts (interfaces + DTOs) and prove behavior with contract tests.
In short: DDD decides what to model and where boundaries are. ALD decides how to encode those boundaries as enforceable, testable contracts.
ALD is a micro-design discipline that lives inside each Bounded Context, turning domain decisions into role interfaces and domain examples into contract tests, enabling safe delegation of implementation (including to AI).
How ALD complements DDD
DDD excels at
- Finding Bounded Contexts and ownership boundaries
- Creating Ubiquitous Language shared by business and engineering
- Modeling Aggregates, entities, value objects, and invariants
- Using Domain Events for decoupling
- Protecting the domain via Anti-Corruption Layers (ACLs)
ALD adds
- Role-based interfaces that represent decisions/policies (not layers)
- DTOs and types that force the language to “stick” in code
- Contract tests that encode domain examples as executable truth
- Ports/adapters discipline that keeps infrastructure at the edges
- Safer scaling of implementation (teams or AI) behind stable contracts
Where ALD sits in a DDD stack
ALD is the micro-design discipline inside each bounded context, turning domain intent into contracts and tests.
Ubiquitous Language, bounded contexts, aggregates, invariants, domain events, ACLs.
Role interfaces + DTOs express decisions; contract tests prove behavior and edge cases.
Teams/AI implement behind contracts; infrastructure stays behind adapters; refactor safely under locked behavior.
Ubiquitous Language → DTOs, types, and test names
Make the language “stick”
- Prefer domain types over primitives (
Money,RiskBand,LoanToValue) - DTOs encode invariants and meaning (not just data transport)
- Interfaces are named for responsibilities (
EligibilityPolicy, notEligibilityService)
Use tests as executable examples
- Write test names like business sentences
- Capture edge cases and “reason codes” explicitly
- Make ambiguity visible and resolved in tests
Examples of “language-first” test names
rejects_application_when_ltv_exceeds_policy_limit()
returns_reason_codes_for_compliance_reporting()
accepts_application_when_income_coverage_meets_threshold()
Bounded Contexts → explicit ports and context-specific DTOs
ALD strengthens context boundaries by requiring that cross-context communication happens through explicit ports and purpose-built DTOs.
ALD boundary rule (DDD-friendly)
Everything that crosses a bounded context boundary must go through:
- an explicit port (interface)
- a context-specific DTO (no shared domain model)
- a translator/mapper if language differs
This reduces the “shared model” anti-pattern and keeps contexts independently evolvable.
What this prevents
- Leaking persistence or vendor SDKs into the domain
- Cross-context coupling through shared entity classes
- Implicit “integration behavior” hidden in services
- Unbounded ripple effects during change
Aggregates, repositories, and ALD’s ISP-safe stance
DDD repositories often become CRUD buckets. ALD keeps persistence ports responsibility-scoped to preserve ISP and aggregate invariants.
Avoid “CRUD repository buckets”
A single interface exposing broad CRUD often forces clients to depend on methods they don’t use (ISP violation).
interface OrderRepository {
save(Order o);
findById(OrderId id);
findByCustomer(CustomerId id);
delete(OrderId id);
}
Prefer capability-focused ports
Split by responsibility and align to aggregate needs.
interface OrderStore { void save(OrderSnapshot snapshot); }
interface OrderLookup { Optional<OrderView> findById(OrderId id); }
interface CustomerOrderQuery { List<OrderSummary> findFor(CustomerId id); }
interface OrderUniquenessCheck { boolean isDuplicate(OrderDraft draft); }
Domain events and Anti-Corruption Layers (ACL)
ALD and DDD overlap strongly here: events and integrations become explicit roles with clean adapters at the edges.
Domain events (DDD) → ALD roles
DomainEventPublisherDomainEventHandler<T>EventOutbox(if using transactional outbox)IntegrationEventMapper(domain → integration)
These roles are testable without infrastructure, keeping the domain pure.
ACL (DDD) → Adapters + translators
- External system behind a port (e.g.,
ExternalCustomerGateway) - Translation isolated (e.g.,
CustomerTranslator) - Sync decisions explicit (e.g.,
CustomerSyncPolicy)
Vendor churn stays at the edges; the domain model stays stable.
A practical DDD + ALD recipe
Use this as a repeatable approach when building or modernizing a bounded context.
- Pick a bounded context Choose one with clear ownership and language.
- Identify top decisions List the 5–15 decisions/policies the context must make (eligibility, pricing, approvals, validation).
- Create role interfaces Name each decision as a role (Strategy-friendly) and keep it single-responsibility.
- Define DTO vocabulary Introduce domain types and DTOs with invariants that express ubiquitous language.
- Write contract tests Encode domain examples as executable tests; make assumptions explicit.
- Delegate implementation AI/teams implement behind contracts; keep frameworks/vendors in adapters.
- Lock contracts, evolve additively Prefer new implementations over modifying existing behavior; refactor internals safely.
What to watch out for
ALD is powerful, but you want to avoid a few DDD pitfalls that can be amplified by over-abstraction.
Common pitfalls
- Role explosion: creating interfaces everywhere without clear benefit
- Anemic domain: pushing all logic into “policies” that should live on aggregates/value objects
- Cross-context type sharing: using the same entity/DTO across bounded contexts
- Over-strategizing: forcing patterns where simple functions would do
ALD guardrails
- Create interfaces where variation, governance, or testability matters
- Keep invariants and behavior on aggregates/value objects when appropriate
- Use explicit ports + translators for cross-context integration
- Prefer composition; introduce patterns only when they earn their keep
Rule of thumb: if behavior is stable and has one implementation, keep it concrete until variability is needed.