Skip to content

Ordering — version 1

Ordering bounded context — taking and pricing a customer’s pizza order. This is the heart of the template: the Sales aggregate (root Order) is the headline aggregate, its lifecycle is an explicit state machine, it is priced from its line items, and it publishes OrderPlaced so Kitchen, Delivery and Payment can react.

classDiagram
    class Fulfillment {
        <<enumeration>>
        DineIn
        Pickup
        Delivery
    }
    class OrderStatus {
        <<enumeration>>
        Draft
        Placed
        InKitchen
        OutForDelivery
        Completed
        Cancelled
    }
    class Money {
        <<value object>>
        +Decimal amount
        +Currency currency
    }
    class OrderOpened {
        <<event>>
        +OrderId orderId
        +CustomerId customer
        +Int lineCount
    }
    class OrderPlacedInternally {
        <<event>>
        +OrderId orderId
        +Int lineCount
    }
    class OrderCancelled {
        <<event>>
        +OrderId orderId
    }
    class OrderLine {
        <<value object>>
        +PizzaCode pizza
        +Int quantity
        +Money unitPrice
        +Money /lineTotal
        +Money /payable
    }
    class Order {
        <<aggregate root>>
        +Int version
        +OrderId id
        +CustomerId customer
        +Fulfillment fulfillment
        +List~OrderLine~ lines
        +OrderStatus status
        +Instant placedAt
        +Money /total
        +Int /lineCount
        +Bool /isPlaced
        +Bool /isCancelled
        +Bool /isDelivery
        +open(CustomerId customer, Fulfillment fulfillment, List~OrderLine~ lines) Order
        +place()
        +cancel()
    }
    OrderOpened --> Order
    OrderPlacedInternally --> Order
    OrderCancelled --> Order
    OrderLine *-- Money
    Order *-- Fulfillment
    Order *-- OrderLine
    Order *-- OrderStatus
    Order *-- Money

The order aggregate. versioned adds an optimistic-concurrency token; the repository block tunes the mutating set and adds intention-revealing finders; being an aggregate it also joins the context IUnitOfWork.

Root entity: Order (identified by OrderId)

Repository finders:

  • byCustomer(customer: CustomerId): List<Order>
  • mostRecent(customer: CustomerId): Order

Repository operations: getById, add, update

OrderLine

One line of an order: a pizza, how many, and the unit price. The pricing value object of the template — it derives its own totals.

FieldTypeDescription
pizzaPizzaCode
quantityIntHow many of this pizza. At least one.
unitPriceMoney
lineTotalMoneyderived
payableMoneyderived — What the customer actually pays for this line (10% off at 5+ pizzas).

Business rules

  • an order line needs at least one pizza

OrderOpened

Raised when an order is opened by the factory.

FieldTypeDescription
orderIdOrderId
customerCustomerId
lineCountInt

OrderPlacedInternally

Raised when an order is placed for processing.

FieldTypeDescription
orderIdOrderId
lineCountInt

OrderCancelled

Raised when an order is cancelled.

FieldTypeDescription
orderIdOrderId
stateDiagram-v2
    [*] --> Draft
    Draft --> Placed
    Draft --> Cancelled
    Placed --> InKitchen
    Placed --> Cancelled
    InKitchen --> OutForDelivery
    InKitchen --> Completed
    InKitchen --> Cancelled
    OutForDelivery --> Completed
    Completed --> [*]
    Cancelled --> [*]
FromToGuard
DraftPlaced
DraftCancelled
PlacedInKitchen
PlacedCancelled
InKitchenOutForDelivery
InKitchenCompleted
InKitchenCancelled
OutForDeliveryCompleted
Completed(terminal)
Cancelled(terminal)

Place a drafted order for processing, stamping the time and recording a domain event. The cross-context announcement is OrderPlaced.

Preconditions:

  • only a draft order can be placed
  • cannot place an empty order

Effects:

  • status -> Placed
  • placedAt -> now

Events:

  • OrderPlacedInternally(orderId: id, lineCount: lines.count)

Cancel an order that has not yet gone out for delivery.

Preconditions:

  • an order already out for delivery cannot be cancelled
  • a completed order cannot be cancelled

Effects:

  • status -> Cancelled

Events:

  • OrderCancelled(orderId: id)
open(customer: CustomerId, fulfillment: Fulfillment, lines: List<OrderLine>)
Section titled “open(customer: CustomerId, fulfillment: Fulfillment, lines: List<OrderLine>)”

Factory-only creation: open a draft order for a customer. The presence of any create makes the all-args constructor private, so callers must go through Order.Open(...).

Preconditions:

  • cannot open an empty order

Events:

  • OrderOpened(orderId: id, customer: customer, lineCount: lines.count)

Business rules

  • every line needs a positive quantity
  • no duplicate pizzas in an order
  • status == Draft when lines.isEmpty

A monetary amount in a specific currency. Never negative. Currency is a shared-kernel type owned jointly with Menu (see context-map.koi) — the same enum flows through both contexts without translation.

FieldTypeDescription
amountDecimal
currencyCurrency

Business rules

  • an amount cannot be negative

How the customer wants the order fulfilled. Drives routing into Delivery.

Values: DineIn, Pickup, Delivery

The lifecycle state of an order. Drives the states status { … } machine on the entity, so only the transitions declared there are legal at runtime.

Values: Draft, Placed, InKitchen, OutForDelivery, Completed, Cancelled

Announced to the rest of the system when an order is placed. An integration event is a published language — its fields stay primitive (ids/scalars/enums), never leaking internal value objects. Kitchen, Delivery and Payment all subscribe to it (authorized by the open-host relations).

FieldTypeDescription
orderIdOrderId
customerCustomerId
fulfillmentFulfillment
totalDecimal
placedAtInstant

Published by this context.

The application/use-case service interface (IOrderingService). Each use case maps to one async method; a context with aggregates also gets a UoW.

  • PlaceOrder(customer: CustomerId, fulfillment: Fulfillment, lines: List<OrderLine>): OrderId

  • CancelOrder(order: OrderId)

KindCoveredTotal
aggregate11
entity11
enum22
event33
integrationEvent11
query01
readModel01
value22

⚠️ 2 not documented