Skip to content

event-handler

Reaction to an Event that produces an externally observable side effect. See Concepts: Event Handler.

Schema

properties:
    scope:
        type: object
        properties:
            domain:
                type: string
                pattern: "^[a-z][a-z0-9-]*$"
        required: [domain]
        additionalProperties: false
    deliveryGuarantee:
        type: string
        enum: [at-least-once, at-most-once]
    idempotency:
        type: object
        properties:
            owner:
                type: string
                enum:
                    - self
                    - downstream
                    - infrastructure
                    - none
                    - not-required
            strategy:
                type: string
        required: [owner]
        additionalProperties: false
    handles:
        type: array
        minItems: 1
        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-]*$"
                  required: [boundedContext, aggregate, event]
                  additionalProperties: false
                - type: object
                  properties:
                    boundedContext:
                        type: string
                        pattern: "^[a-z][a-z0-9-]*$"
                    event:
                        type: string
                        pattern: "^[a-z][a-z0-9-]*$"
                  required: [boundedContext, event]
                  additionalProperties: false
    constraints:
        type: array
        items:
            type: object
            properties:
                name:
                    type: string
                    pattern: "^[a-z][a-z0-9-]*$"
                rule:
                    type: string
            required: [name, rule]
            additionalProperties: false
    sideEffects:
        type: array
        minItems: 1
        items:
            oneOf:
                - properties:
                    type:
                        const: external-call
                    externalSystem:
                        type: string
                        pattern: "^[a-z][a-z0-9-]*$"
                    rule:
                        type: string
                  required: [type, externalSystem, rule]
                  additionalProperties: false
                - properties:
                    type:
                        const: other
                    rule:
                        type: string
                  required: [type, rule]
                  additionalProperties: false
required: [scope, deliveryGuarantee, handles, sideEffects]
allOf:
    - if:
        properties:
            deliveryGuarantee:
                const: at-least-once
        required: [deliveryGuarantee]
      then:
        required: [idempotency]

Anatomy

An Event Handler scopes to a Domain via scope.domain. Domain-scope is appropriate because handlers often coordinate with External Systems that sit at the edge of the Domain rather than inside any single Bounded Context.

The required deliveryGuarantee field carries one of two values: at-least-once or at-most-once. The two are not symmetric in their consequences. With at-least-once, the schema enforces that idempotency is also present, because the model has to say who absorbs the duplicates that the delivery guarantee admits. With at-most-once, idempotency is optional.

The idempotency object documents who tolerates duplicate delivery and, optionally, how. idempotency.owner selects from a closed set of values: self means the handler deduplicates internally, downstream means a system the handler calls handles it, infrastructure means the delivery layer deduplicates, none means no mechanism is in place – a deliberate, dangerous choice – and not-required means the handler is naturally idempotent. The optional idempotency.strategy carries free-form prose where owner alone is not self-explanatory.

The handles array lists Event references with at least one entry. Each reference is a discriminated oneOf: Aggregate-bound entries carry boundedContext, aggregate, and event, while BC-scoped entries carry boundedContext and event. Both shapes are required to be complete – partial references are rejected.

The sideEffects array lists what the handler causes in the world, with at least one entry. Each entry is a discriminated oneOf on type. When type is external-call, the entry carries the target External System name in externalSystem plus a prose rule describing the behavior. When type is other, the entry carries only rule and is meant for any observable effect that is not a third-party invocation – audit logs, metrics, cache invalidation, and the like.

The optional constraints array holds named rules under which the handler runs. Each entry is { name, rule }. Constraints sit outside idempotency and sideEffects; they describe activation conditions, not the side effect itself.

description and metadata carry the usual free-form prose and non-semantic attachments. apiVersion is schema.esdm.io/core/v1, kind is event-handler, and name is the Event Handler's kebab-case identifier.