Skip to content

Schema Generation

Protean can generate standard JSON Schema (Draft 2020-12) documents for all data-carrying domain elements: aggregates, entities, value objects, commands, events, and projections.

Generated schemas are useful for:

  • Contract testing — validate that payloads conform to expected shapes
  • Schema registries — publish schemas to Confluent Schema Registry, AWS Glue, or similar
  • OpenAPI integration — JSON Schema Draft 2020-12 is natively supported by OpenAPI 3.1
  • Documentation — auto-generate API docs from schema definitions
  • Code generation — generate client SDKs in other languages from schemas

Generating schemas

Use the protean schema generate command to produce JSON Schema files for every data-carrying element in your domain.

From a live domain

protean schema generate --domain=my_app.domain

This loads the domain, builds the IR, and writes schemas to .protean/schemas/.

From an IR file

protean schema generate --ir=domain-ir.json

If you already have a serialized IR (from protean ir show), you can generate schemas directly from it.

Custom output directory

protean schema generate --domain=my_app.domain --output=build

Inspecting a single schema

Use protean schema show to print the JSON Schema for a specific element:

# By short name
protean schema show OrderPlaced --domain=my_app.domain

# By fully qualified name
protean schema show my_app.ordering.OrderPlaced --domain=my_app.domain

# Raw JSON for piping
protean schema show OrderPlaced --domain=my_app.domain --raw | jq .

If multiple elements share the same short name, the command lists all matching FQNs and asks you to disambiguate.


Output structure

Schemas are organized by aggregate cluster:

.protean/
├── ir.json
└── schemas/
    ├── Order/
    │   ├── aggregates/
    │   │   └── Order.v1.json
    │   ├── commands/
    │   │   └── PlaceOrder.v1.json
    │   ├── entities/
    │   │   └── LineItem.v1.json
    │   ├── events/
    │   │   └── OrderPlaced.v2.json
    │   └── value_objects/
    │       └── Money.v1.json
    └── projections/
        └── OrderDashboard.v1.json

Filenames include the element version (v1, v2, etc.) for events and commands. Other element types default to v1.


Schema structure

Each generated schema is a standard JSON Schema object with Protean-specific metadata in x-protean-* extension fields:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "OrderPlaced",
  "type": "object",
  "properties": {
    "order_id": { "type": "string" },
    "customer_name": { "type": "string" },
    "total": { "type": "number" }
  },
  "required": ["customer_name", "order_id", "total"],
  "x-protean-element-type": "event",
  "x-protean-fqn": "my_app.ordering.OrderPlaced",
  "x-protean-aggregate": "my_app.ordering.Order",
  "x-protean-version": 1,
  "x-protean-type": "Ordering.OrderPlaced.v1"
}

Extension fields

Field Description
x-protean-element-type Element kind: aggregate, entity, value_object, command, event, projection
x-protean-fqn Fully qualified name in the domain
x-protean-aggregate FQN of the owning aggregate
x-protean-version Event/command version number
x-protean-type Message type string
x-protean-published true if the event is part of the published contract
x-protean-is-fact-event true if auto-generated from aggregate changes
x-protean-auto-generated true if generated by the framework
x-protean-identity-field Identity field name (aggregates only)
x-protean-is-event-sourced true if the aggregate uses event sourcing

Nested structures

Value objects and entities referenced by an aggregate are resolved as $defs with $ref pointers:

{
  "properties": {
    "shipping_address": { "$ref": "#/$defs/ShippingAddress" }
  },
  "$defs": {
    "ShippingAddress": {
      "type": "object",
      "properties": {
        "street": { "type": "string", "maxLength": 255 },
        "city": { "type": "string", "maxLength": 100 }
      },
      "required": ["city", "street"]
    }
  }
}

Optional fields

Optional fields use the anyOf pattern with null:

{
  "total": {
    "anyOf": [
      { "type": "number", "minimum": 0.0 },
      { "type": "null" }
    ]
  }
}

Validating payloads

Generated schemas work with any JSON Schema validator. For example, using Python's jsonschema library:

import json
import jsonschema

with open(".protean/schemas/Order/events/OrderPlaced.v1.json") as f:
    schema = json.load(f)

payload = {
    "order_id": "order-123",
    "customer_name": "Alice",
    "total": 99.99,
}

jsonschema.validate(payload, schema)  # Passes

Design decisions

  • IR-first generation — schemas are built from IR field metadata, not Pydantic's model_json_schema(). See ADR-0005.
  • Standard JSON Schema with x- extensions — not a custom envelope. See ADR-0006.