Domain-driven design is a set of questions, not a Kekonic dialect. Use the same kinds and groups
you already have. Keep each picture at one zoom: discovery timeline, context relationships, or one
aggregate’s consistency boundary.
Walk time left to right. Prefer kinds over a second sticky-note palette: commands, domain events
(past tense), aggregates, policies, and people. Phase groups are facilitation columns, not bounded
contexts.
Edit source — diagram updates live
Source
kdiagram 1
diagram "Order domain — event storm" {
layout {
direction: LR
density: compact
considerModelOrder: true
arrange: row
align: stretch
}
edges {
route: orthogonal
crossings: gaps
}
presentation {
title: auto
titleSubtitle: "Discovery timeline from catalog browse through payment to fulfillment."
groupAccent: false
showKindSubtitles: true
}
group browse "Browse" {
arrange: stack
shopper: person "Shopper"
browseCatalog: command "Browse catalog"
catalogShown: event "CatalogShown" {
styles: ["info"]
}
}
group checkout "Checkout" {
arrange: stack
placeOrder: command "Place order"
order: aggregate "Order"
paymentPolicy: policy "Must authorize before confirm"
paymentAuthorized: event "PaymentAuthorized" {
styles: ["success"]
}
orderPlaced: event "OrderPlaced" {
styles: ["info"]
}
}
group fulfill "Fulfill" {
arrange: stack
reserveStock: command "Reserve stock"
stockReserved: event "StockReserved"
pickReleased: event "PickReleased"
outOfStock: event "ReservationFailed" {
styles: ["warning"]
}
}
shopper -> browseCatalog
browseCatalog -> catalogShown
catalogShown -> placeOrder
placeOrder -> order
order -> paymentPolicy
paymentPolicy -> paymentAuthorized "authorized"
paymentAuthorized -> orderPlaced
orderPlaced -> reserveStock
reserveStock -> stockReserved
stockReserved -> pickReleased
reserveStock -> outOfStock "no stock"
animation "Place order" {
dim *
activate shopper
wait 200ms
flow shopper -> browseCatalog -> catalogShown -> placeOrder for 1s
activate placeOrder
flow placeOrder -> order for 400ms
activate order
pulse order for 600ms
flow order -> paymentPolicy -> paymentAuthorized -> orderPlaced for 900ms
activate orderPlaced
pulse orderPlaced for 800ms
wait 400ms
}
}
Keep the workshop honest: do not invent events the experts did not name. Style the pivotal events
(info) and failures (warning) so the eye can find them.
Draw each bounded context as a boundary. Label edges in plain language, then the pattern
(customer/supplier, anticorruption, open-host, conformist). Do not use acronyms alone.
Edit source — diagram updates live
Source
kdiagram 1
diagram "Commerce bounded contexts" {
layout {
direction: LR
density: spacious
considerModelOrder: true
groupLayout: compound
}
edges {
route: orthogonal
crossings: gaps
}
presentation {
title: auto
titleSubtitle: "How catalog, ordering, payments, and fulfillment relate — not a C4 container view."
groupAccent: true
showKindSubtitles: false
}
boundary identity "Identity" {
accounts: service "Accounts"
}
boundary catalog "Catalog" {
products: service "Product catalog"
}
boundary ordering "Ordering" {
orders: aggregate "Order"
}
boundary payments "Payments" {
charges: service "Charges"
}
boundary fulfillment "Fulfillment" {
stock: service "Inventory"
}
accounts -> orders "Customer ids — published language" { priority: high }
products -> orders "Product snapshot — anticorruption" { priority: high }
orders -> charges "Authorize charge — customer / supplier" { priority: high }
charges -> orders "Payment result — conformist"
orders -> stock "OrderPlaced contract — open host" { priority: high }
stock -> orders "Reservation result — customer / supplier"
animation "Checkout integration" {
dim *
activate orders
wait 250ms
flow orders -> charges for 700ms
activate charges
pulse charges for 800ms
flow charges -> orders for 700ms
activate orders
wait 400ms
flow orders -> stock for 700ms
activate stock
wait 500ms
}
}
A context map is not a C4 system context: people and external software systems belong on the C4
picture; relationship patterns between contexts belong here.
Open one aggregate. Show the command that mutates the root, the entities that cannot persist
outside it, value objects as muted cards, and the invariant the root protects. Emit the domain
event that other contexts already know.
Edit source — diagram updates live
Source
kdiagram 1
diagram "Order aggregate" {
layout {
direction: LR
density: compact
considerModelOrder: true
groupLayout: compound
}
edges {
route: orthogonal
crossings: gaps
}
presentation {
title: auto
titleSubtitle: "What the Order aggregate protects in one transaction — not ports and adapters."
groupAccent: true
showKindSubtitles: true
}
placeOrder: command "PlaceOrder"
boundary orderAgg "Order aggregate" {
arrange: stack
order: aggregate "Order" {
description: "Root. Owns status, totals, and the outbox record."
}
line: component "Order line" {
description: "Quantity and product snapshot. Cannot persist alone."
subtitle: "Entity"
}
money: component "Money" {
description: "Amount plus currency."
subtitle: "Value object"
styles: ["muted"]
}
invariant: policy "Lines must share the order currency"
}
placed: event "OrderPlaced" {
styles: ["info"]
}
placeOrder -> order { priority: high }
order -> line "contains"
line -> money "priced as"
order -> invariant
order -> placed { priority: high }
animation "PlaceOrder" {
dim *
activate placeOrder
wait 200ms
flow placeOrder -> order for 500ms
activate order
pulse order for 800ms
flow order -> line for 400ms
activate line
flow order -> invariant for 400ms
activate invariant
pulse invariant for 700ms
flow order -> placed for 500ms
activate placed
wait 500ms
}
}
Do not turn this view into a class diagram or a ports-and-adapters hexagon. Those are different
questions.
Name an animation that teaches a real path. Use icons only when they add information beyond kind
and shape. Use semantic styles (success, warning, muted, info) for outcomes and secondary
roles — not as decoration.