feature¶
Top-level Given-When-Then document. A feature carries one or more scenarios about one consistency unit.
Schema¶
type: object
properties:
apiVersion:
const: schema.esdm.io/given-when-then/v1
kind:
const: feature
name:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description:
type: string
metadata:
type: object
properties:
labels:
type: object
additionalProperties:
type: string
annotations:
type: object
additionalProperties:
type: string
additionalProperties: false
scope:
oneOf:
- type: object
properties:
domain:
type: string
pattern: "^[a-z][a-z0-9-]*$"
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
aggregate:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [domain, boundedContext, aggregate]
additionalProperties: false
- type: object
properties:
domain:
type: string
pattern: "^[a-z][a-z0-9-]*$"
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
dynamicConsistencyBoundary:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [domain, boundedContext, dynamicConsistencyBoundary]
additionalProperties: false
- type: object
properties:
domain:
type: string
pattern: "^[a-z][a-z0-9-]*$"
processManager:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [domain, processManager]
additionalProperties: false
- type: object
properties:
domain:
type: string
pattern: "^[a-z][a-z0-9-]*$"
boundedContext:
type: string
pattern: "^[a-z][a-z0-9-]*$"
readModel:
type: string
pattern: "^[a-z][a-z0-9-]*$"
required: [domain, boundedContext, readModel]
additionalProperties: false
scenarios:
type: array
minItems: 1
items:
type: object
properties:
name:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description:
type: string
given:
type: array
when:
type: object
then:
type: object
required: [name, when, then]
additionalProperties: false
required: [apiVersion, kind, name, scope, scenarios]
unevaluatedProperties: false
allOf:
- if:
properties:
scope:
required: [aggregate]
required: [scope]
then:
properties:
scenarios:
items:
properties:
given:
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
- if:
properties:
scope:
required: [dynamicConsistencyBoundary]
required: [scope]
then:
properties:
scenarios:
items:
properties:
given:
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
- if:
properties:
scope:
required: [processManager]
required: [scope]
then:
properties:
scenarios:
items:
properties:
given:
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
- if:
properties:
scope:
required: [readModel]
required: [scope]
then:
properties:
scenarios:
items:
properties:
given:
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
Anatomy¶
A feature targets exactly one consistency unit, and the chosen target fixes the shape of every scenario inside the document. The scope field is a structural oneOf over four variants. The Aggregate variant carries domain, boundedContext, and aggregate. The DCB variant carries domain, boundedContext, and dynamicConsistencyBoundary. The Process Manager variant carries domain and processManager – Process Managers are domain-scoped, so no Bounded Context is involved. The Read Model variant carries domain, boundedContext, and readModel. The presence of aggregate, dynamicConsistencyBoundary, processManager, or readModel is itself the discriminator. Mixing variants inside one feature is forbidden by the schema's per-variant if/then rules.
The scenarios array is required and non-empty. Each entry carries name, given, when, and then, plus an optional description. The shape of given, when, and then depends on the feature's variant; the per-scenario fields are documented on scenario.
The common fields round out the document: apiVersion is schema.esdm.io/given-when-then/v1, kind is feature, name is the feature's kebab-case identifier, description carries free-form prose, and metadata holds non-semantic labels and annotations.