scenario¶
A scenario is a nested entry inside a feature.scenarios[] list. It is not a top-level document on its own; it lives inside its feature and inherits the feature's variant. See feature for the surrounding document and the variant choice it fixes.
Schema¶
The shape of a scenario depends on its surrounding feature's variant. The four variants resolve to the following schemas.
Aggregate variant¶
type: object
properties:
name:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description:
type: string
given:
type: array
items:
type: object
properties:
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [event, data]
additionalProperties: false
when:
type: object
properties:
command:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
actor:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [command, data]
additionalProperties: false
then:
oneOf:
- type: object
properties:
events:
type: array
items:
type: object
properties:
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [event, data]
additionalProperties: false
required: [events]
additionalProperties: false
- type: object
properties:
rejection:
oneOf:
- type: object
properties:
invariant:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [invariant]
additionalProperties: false
- type: object
properties:
reason:
type: string
minLength: 1
required: [reason]
additionalProperties: false
required: [rejection]
additionalProperties: false
required: [name, when, then]
additionalProperties: false
DCB variant¶
type: object
properties:
name:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description:
type: string
given:
type: array
items:
oneOf:
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
aggregate:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, aggregate, event, data]
additionalProperties: false
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, event, data]
additionalProperties: false
when:
type: object
properties:
command:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
actor:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [command, data]
additionalProperties: false
then:
oneOf:
- type: object
properties:
events:
type: array
items:
type: object
properties:
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [event, data]
additionalProperties: false
required: [events]
additionalProperties: false
- type: object
properties:
rejection:
oneOf:
- type: object
properties:
invariant:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [invariant]
additionalProperties: false
- type: object
properties:
reason:
type: string
minLength: 1
required: [reason]
additionalProperties: false
required: [rejection]
additionalProperties: false
required: [name, when, then]
additionalProperties: false
Process Manager variant¶
type: object
properties:
name:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description:
type: string
given:
type: array
items:
oneOf:
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
aggregate:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, aggregate, event, data]
additionalProperties: false
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, event, data]
additionalProperties: false
when:
oneOf:
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
aggregate:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, aggregate, event, data]
additionalProperties: false
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, event, data]
additionalProperties: false
- type: object
properties:
timer:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [timer]
additionalProperties: false
then:
type: object
properties:
emits:
type: array
items:
oneOf:
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
aggregate:
type: string
pattern: "^[a-z][a-z0-9-]*$"
command:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, aggregate, command]
additionalProperties: false
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
dynamicConsistencyBoundary:
type: string
pattern: "^[a-z][a-z0-9-]*$"
command:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, dynamicConsistencyBoundary, command]
additionalProperties: false
setTimers:
type: array
items:
type: string
pattern: "^[a-z][a-z0-9-]*$"
cancelTimers:
type: array
items:
type: string
pattern: "^[a-z][a-z0-9-]*$"
state:
type: object
ended:
const: true
minProperties: 1
additionalProperties: false
required: [name, when, then]
additionalProperties: false
Read Model variant¶
type: object
properties:
name:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description:
type: string
given:
type: array
items:
oneOf:
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
aggregate:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, aggregate, event, data]
additionalProperties: false
- type: object
properties:
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
event:
type: string
pattern: "^[a-z][a-z0-9-]*$"
data:
type: object
required: [boundedContext, event, data]
additionalProperties: false
when:
type: object
properties:
query:
type: string
pattern: "^[a-z][a-z0-9-]*$"
parameters:
type: object
required: [query, parameters]
additionalProperties: false
then:
oneOf:
- type: object
properties:
result: true
required: [result]
additionalProperties: false
- type: object
properties:
readModel: true
required: [readModel]
additionalProperties: false
required: [name, when, then]
additionalProperties: false
Anatomy¶
Every scenario carries four fields. The required name is a kebab-case identifier that uniquely names the scenario within its feature. The optional description carries free-form prose. The required given, when, and then describe the scenario itself, and their shape depends on which variant the surrounding feature has chosen.
given is always a list and is always required, but the list may be empty. An empty given means "no preceding history" – the consistency unit is in its initial state when the scenario starts. The shape of each given entry depends on the variant: bare for Aggregate features, scoped for the rest.
The Aggregate variant uses bare-name Events in given. Each entry carries event (the bare Event name) and data (the concrete payload value, an empty object for an Event without a payload). The when of an Aggregate scenario carries command (the bare Command name), data (the concrete payload value), and an optional actor (the Actor name, useful for permission-style scenarios). The then of an Aggregate scenario is one of two shapes: events, listing { event, data } entries with bare Event names – the empty list is legal and expresses idempotency – or rejection, an expected refusal expressed as either { invariant: <name> } or { reason: <prose> }.
The DCB variant uses scoped Event references in given, mirroring the structure of consults on the DCB itself. Each entry carries boundedContext, event, data, and (when the Event is Aggregate-bound) aggregate. The when and then shapes match the Aggregate variant, because the DCB still produces Events from a single triggering Command.
The Process Manager variant uses scoped Event references in given, with the same shape as the DCB variant. The when of a Process Manager scenario is a oneOf over three shapes: an Aggregate-owned Event delivered to the instance (carrying boundedContext, aggregate, event, data), a free-standing Event delivered to the instance (carrying boundedContext, event, data), or a tick of a named timer (carrying timer). The then of a Process Manager scenario is an object that may carry any of emits (Command references with their data, Aggregate-bound or DCB-bound), setTimers (timer names the reaction arms), cancelTimers (timer names the reaction cancels), state (the concrete state value of the instance after the reaction), or ended (true when the reaction is expected to retire the instance; false is not meaningful and should be omitted).
The Read Model variant uses scoped Event references in given so the projection state can be set up. The when of a Read Model scenario carries query (the bare Query name) and parameters (the concrete parameter value). The then is one of two shapes: result, which is the expected query result and is intentionally free-form because result shapes are paradigm-specific, or readModel, which is the expected materialized Read Model content.
description is the only optional top-level field on a scenario. The common document-level fields – apiVersion, kind, name, metadata – are not present on a scenario; they live on the surrounding feature.