Adaptor
An adaptor's purpose is to adapt one Context to another Context. In Domain-Driven Design, this concept is known as an anti-corruption layer that keeps the ubiquitous language of one context from "corrupting" the language of another context. The authors of RIDDL didn't like that term for a variety of reasons so we have renamed the concept as adaptor in RIDDL. Same idea, different name.
Message Translation¶
Adaptors do their work at the level of messages sent between Contexts. This is done using one or more Handlers. Each handler specifies how messages are translated into other messages and forwarded to the target context.
Target Context¶
Adaptors are only definable within a containing Context which provides one participant of the translation. The other Context, known as the target context, is specified within the definition of the adaptor.
Adaptation Directionality¶
Adaptors only translate in one direction, between the containing context and the target context. However, multiple Adaptors can be defined to achieve bidirectional adaptation between Contexts. The directionality of an Adaptor is specified in the definition of the adaptor. This leads to two kinds of adaptors: inbound and outbound.
One adaptor per direction, per pair of contexts
A context may adapt both to and from another context, but only once in each direction. Two adaptors with the same direction to the same foreign context split that context's translation across two places, with nothing to say which one handles a given message — an Error, because the ambiguity has no defensible resolution.
Direction is part of the rule. Inbound plus outbound between the same pair is the sanctioned way to say "both ways", not duplication. And adaptors owned by different contexts are equally fine: A adapting from B while B adapts from A is two contexts each defending its own model.
Inbound Adaptors¶
Inbound adaptors provide an adaptation that occurs from the Context referenced in the adaptor to the Context containing the adaptor.
Outbound Adaptors¶
Outbound adaptors provide an adaptation that occurs from the Context containing the adaptor to the Context referenced in the adaptor.
Syntax¶
context Orders is {
record OrderData is { orderId is String, isPaid is Boolean }
command MarkAsPaid is { orderId is String }
command HandlePaymentFailure is { orderId is String }
command ReserveItems is { orderId is String }
entity Order is {
state Active of record OrderData is {
handler OrderHandler is { on command MarkAsPaid { ??? } }
}
}
adaptor PaymentAdapter from context Payments is {
handler InboundPayments is {
on paid: event Payments.PaymentCompleted {
tell command MarkAsPaid(paid.orderId) to entity Order
}
on event Payments.PaymentFailed {
tell command HandlePaymentFailure to entity Order
}
on other {
error "Unrecognized message from the Payments context"
}
}
} with {
briefly as "Translates payment messages between Orders and Payments"
}
adaptor InventoryAdapter to context Inventory is {
handler OutboundInventory is {
on command ReserveItems {
tell command Inventory.ReserveStock to context Inventory
}
on other {
error "Unrecognized outbound message"
}
}
} with {
briefly as "Translates inventory requests from Orders to Inventory"
}
}
Note the operand order: tell <message> to <processor>, not the reverse.
The Isolation Seam¶
An adaptor bridges exactly two contexts: the context that contains it, and
the referent context named in its declaration. It is the only sanctioned
crossing point between contexts, so it must not traffic in a third
context's messages.
Validation
Errors:
- A message whose owning context is neither the parent nor the referent.
This applies both to the message an
onclause consumes and to everysend/telltarget it emits, including those nested insidewhen,matchandforeachbodies. - A handler with no
on otherclause. An adaptor must say explicitly what it does with messages it does not recognize, rather than discarding them silently.
Types defined at domain or root level are shared vocabulary common to both sides and are never flagged.
Options¶
circuit-breaker (0–2 arguments) is valid on an adaptor, tripping the
adaptation when the far side is failing.
When to Use Adaptors¶
Use an adaptor when:
- Contexts have different vocabularies: The same concept has different names or structures in each context
- You need to protect domain integrity: Prevent external concepts from leaking into your bounded context
- Contexts evolve independently: Changes in one context shouldn't force changes in another
- Integration with external systems: Translate between your domain model and external APIs
Example scenario: Your Orders context tracks "line items" while the Inventory context uses "stock reservations". An adaptor translates between these models so neither context needs to know about the other's terminology.
Adaptor vs. Direct References¶
| Approach | When to Use |
|---|---|
| Adaptor | Contexts have different models, need translation |
| Direct reference | Contexts share the same model, tightly coupled by design |
Occurs In¶
Contains¶
flowchart TD
Adaptor(["Adaptor"]) --> Handler
Adaptor --> PC["Processor contents"]