Relay / fictional worked example
Relay behavioural contracts
On this page
Interfaces are only useful context boundaries when they define behaviour, including failure behaviour. These contracts are normative for the fictional example; the runnable demonstration implements only the intake subset.
Request record
| Field | Meaning | Invariant |
|---|---|---|
id | Opaque request identifier | Stable after creation; no meaning inferred from its format |
source | Submission source name | Non-empty string |
external_id | Identifier assigned by that source | Unique within the source for this logical submission |
site_id | Site associated with the request | Non-empty; authoritative tenancy checks required in production |
summary | Reporter-entered short description | Non-empty after trimming |
status | Current request state | One of new, assigned, resolved |
assignee_id | Current owner | null until assigned |
version | Optimistic record version | Positive integer; begins at 1 |
The example JSON Schema lives at ../../04-executable-example/contracts/request.schema.json.
RequestStore.create_if_absent
Inputs: source, external_id, site_id, summary.
Expected results:
- New key: returns the created
Requestandcreated=true. - Replay, same key and same canonical input: returns the original
Requestandcreated=false, without changing fields or version. - Replay, same key but changed input: raises
DuplicatePayloadConflict; must not replace or merge the original. - Invalid input: raises
ValidationError, no record is written.
Idempotency key is exactly (source, external_id) in the teaching example. This is not fuzzy deduplication. Two reports with the same text but different external IDs are distinct.
Important subtlety
If external identifiers are unique only within a tenant, the correct production key may be (tenant_id, source, external_id). The example assumes source namespaces the identifier across the store. That assumption requires explicit validation in a real integration; do not silently port it to a multi-tenant release.
RequestIntake.submit
The integration calls the store and emits RequestCreated to the audit sink only when created=true. On a replay, the audit event is not duplicated. On a conflicting duplicate or validation failure, no creation event is emitted.
A real application must consider what happens if writing the request succeeds but recording the event fails. Possible approaches include a transactional outbox or idempotent retry. The in-memory example is not transactionally durable; this is a release-blocking design decision for a production adapter.
AssignmentWorkflow.assign (design target, not included in demo code)
Inputs: request ID, authorised actor, assignee ID, expected version.
Expected behaviour:
- Reject if actor lacks permission for that request’s site or tenant.
- Reject if version no longer matches; never silently overwrite another assignment.
- In the initial version, allow transition from
newtoassignedonly; reassignment and reopening require separate design. - Record
RequestAssignedwith before/after state and actor. - Request a notification, but do not conflate external notification delivery with a committed state change.
No implementation of this design target is claimed in the runnable demo.
Error contract
| Error | Meaning | Handling intent |
|---|---|---|
ValidationError | Required input is invalid | Surface correction to caller; no side effects |
DuplicatePayloadConflict | Same external key used for different payload | Escalate or reconcile with upstream source; never overwrite |
NotFound | Request ID does not exist | Caller handles known missing state |
VersionConflict | Concurrent update/version mismatch | Refresh and retry only under an agreed policy |
PermissionDenied | Caller lacks rights | No disclosure of restricted request data |
The last three are part of the intended overall design; they are not implemented by the intake teaching slice.
Proving contracts
The runnable tests cover input validation, replay, conflicting duplicates, audit emission and independent submissions. Additional contract tests would be required for persistence, concurrency, multitenancy, recovery and notification delivery.
The definition of ‘done’ is not ‘method exists’. It is ‘the agreed externally observable guarantees have evidence’.