Decorators
Applies to: DDD · CQRS · Event Sourcing
Every domain element in Protean is registered with a decorator on the
Domain instance. The decorator configures the element, wires it into the
domain registry, and accepts options that control its runtime behavior.
Options are passed as keyword arguments:
@domain.aggregate(schema_name="users", fact_events=True)
class User:
name = String(required=True)
All options are accessible at runtime via element.meta_.
Options every element accepts
suppress_checks
Silences named fitness-function
diagnostics for one element. Every decorator on this page accepts it; the
default is ().
| Option | Default | Description |
|---|---|---|
suppress_checks |
() |
Diagnostic codes protean check should not report for this element |
@domain.aggregate(suppress_checks=("AGGREGATE_NO_INVARIANTS",))
class Order: ...
Suppress the narrowest thing that works: one code on one element. It applies to
that element only, so it cannot quietly disable a rule across the domain the way
a config-level suppression can. Reach for it when a rule is wrong about this
element, not when a rule is inconvenient; protean check reports what is
suppressed, so a suppression is a claim you are making in public.
Config-wide suppressions live under [lint].suppressions in
configuration.
Domain Model
Domain.aggregate
The root entity of a consistency boundary. Aggregates encapsulate business logic, enforce invariants, and own the transaction lifecycle.
| Option | Default | Description |
|---|---|---|
abstract |
False |
Cannot be instantiated when True |
auto_add_id_field |
True |
Auto-adds an id identity field |
event_sourced |
False |
Enables event sourcing for this aggregate |
fact_events |
False |
Auto-generates fact events on state changes |
indexes |
() |
List of Index declarations for the persistence layer |
provider |
"default" |
Database provider name |
schema_name |
snake_case(cls) |
Table or collection name |
stream_category |
snake_case(cls) |
Message stream category |
database_model |
None |
Custom database model class |
limit |
100 |
Default query result limit |
reserved |
() |
Names of removed fields that must never be reused. On an event-sourced aggregate, this earns the field_removed change a safe rating in protean ir diff and drops assignments to the name, and calls to its add_<name>/remove_<name> helpers, during replay. Child entities those calls would have added are dropped with them. See Removing a field from an event-sourced aggregate |
Boolean element options are bare predicates (event_sourced, fact_events,
abstract), not is_-prefixed.
Deprecated: is_event_sourced
is_event_sourced is a deprecated alias for event_sourced. It still works
but emits a RemovedInProtean10Warning and is reported as a
DEPRECATED_OPTION diagnostic by protean check; it will be removed in
v1.0.0. If both are supplied, event_sourced wins.
Guide: Aggregates · Declaring Indexes
Domain.entity
An object with identity that lives inside an aggregate. Entities are always accessed through their parent aggregate and cannot exist independently.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. Parent aggregate class |
auto_add_id_field |
True |
Auto-adds an id identity field |
indexes |
() |
List of Index declarations for the persistence layer |
provider |
"default" |
Database provider name |
schema_name |
snake_case(cls) |
Table or collection name |
database_model |
None |
Custom database model class |
limit |
100 |
Default query result limit |
Guide: Entities · Declaring Indexes
Domain.value_object
An immutable object defined entirely by its attributes, with no identity. Two instances with the same attributes are equal.
| Option | Default | Description |
|---|---|---|
abstract |
False |
Cannot be instantiated when True |
part_of |
None |
Owning aggregate or entity (optional) |
Guide: Value Objects
Domain.domain_service
Stateless business logic that spans multiple aggregates. Domain services encapsulate cross-aggregate rules and run invariants for validation.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. List of two or more associated aggregates |
Guide: Domain Services
Messages
Domain.command
An immutable DTO representing an intent to change aggregate state. Named
with imperative verbs (PlaceOrder, RegisterUser).
| Option | Default | Description |
|---|---|---|
abstract |
False |
Cannot be instantiated when True |
part_of |
— | Required. Target aggregate class |
version |
1 |
Schema version (positive integer). Also settable via a __version__ class attribute, see Events |
lenient |
None |
Overrides the domain's lenient_deserialization for this class. True drops unknown fields when reading an old message instead of raising; None follows the domain setting |
Deprecated: published on commands
published describes an event's place in the bounded context's published
language, and means nothing on a command. Passing it emits a
DeprecationWarning; drop it.
Guide: Commands
Domain.event
An immutable fact representing a state change that has occurred. Named in
past tense (OrderPlaced, CustomerRegistered).
| Option | Default | Description |
|---|---|---|
abstract |
False |
Cannot be instantiated when True |
part_of |
— | Required. Aggregate that raises this event |
version |
1 |
Schema version (positive integer). Feeds the vN suffix of the event's type string |
deprecated |
None |
Marks the event deprecated. A dict {"since": ..., "removal": ...} recording the deprecation and planned removal versions |
superseded_by |
None |
Names the replacement event (an Event class or a string). Raising a deprecated event emits a DeprecationWarning naming it |
published |
False |
The event is part of this context's published language, so other contexts may depend on its shape. See Guarantees |
lenient |
None |
Overrides the domain's lenient_deserialization for this class. True drops unknown fields when reading an old message instead of raising; None follows the domain setting |
The schema version can be declared either with the version= decorator
option or with a __version__ class attribute (both default to 1):
@domain.aggregate
class Order:
status = String()
@domain.event(part_of=Order, version=2) # decorator option
class OrderPlaced:
order_id = String()
@domain.event(part_of=Order)
class OrderShipped:
__version__ = 2 # class attribute
order_id = String()
The two forms are equivalent, both drive the vN suffix of the event's type
string (Ordering.OrderPlaced.v2 in a domain named Ordering). Declaring the
version both ways on the same class raises an IncorrectUsageError. The same option is available on @domain.command.
Guide: Events · Reference: Compatibility
Domain.query
An immutable read intent targeting a projection. The read-side counterpart of commands.
| Option | Default | Description |
|---|---|---|
abstract |
False |
Cannot be instantiated when True |
part_of |
— | Required. Associated projection class |
Guide: Projections, Query
Handlers
Domain.command_handler
Receives commands and orchestrates aggregate state changes. Uses
@handle(CommandClass) to route commands to handler methods.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. Target aggregate class |
stream_category |
from aggregate | Message stream category |
subscription_type |
None |
Subscription behavior enum |
subscription_profile |
None |
Subscription profile enum |
subscription_config |
{} |
Custom subscription configuration |
sequential_by |
None |
Field name whose value partitions the stream, so commands sharing a value are processed one at a time. See Sequential processing |
timeout |
None |
Default deadline in seconds for commands this handler processes. Falls back to command_default_timeout; an explicit deadline on domain.process() wins over both |
retries |
None |
Attempts before the message goes to the DLQ. Falls back to the subscription's max_retries |
backoff |
None |
Delay between retries, in seconds. Doubles per attempt |
retry_exceptions |
None |
Exception types worth retrying. Anything else fails straight to the DLQ |
Guide: Command Handlers
Domain.event_handler
Reacts to domain events and orchestrates side effects. Uses
@handle(EventClass) to route events to handler methods. @handle("$any")
routes every event on the handler's stream to a single catch-all method, under
both synchronous and asynchronous processing.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. Source aggregate class |
source_stream |
None |
Custom event stream source |
stream_category |
from aggregate | Message stream category |
subscription_type |
None |
Subscription behavior enum |
subscription_profile |
None |
Subscription profile enum |
subscription_config |
{} |
Custom subscription configuration |
sequential_by |
None |
Field name whose value partitions the stream, so events sharing a value are processed one at a time. See Sequential processing |
retries |
None |
Attempts before the message goes to the DLQ. Falls back to the subscription's max_retries |
backoff |
None |
Delay between retries, in seconds. Doubles per attempt |
retry_exceptions |
None |
Exception types worth retrying. Anything else fails straight to the DLQ |
sequential_by is a no-op unless the configured broker advertises
STREAM_PARTITIONING, so a domain that declares it still runs on the inline
broker in tests, without the ordering guarantee. See the
broker partitioning contract.
Guide: Event Handlers · Sequential processing
Domain.query_handler
Processes queries and returns results from projections. Uses
@read(QueryClass) to route queries to handler methods. Unlike @handle,
@read does not wrap execution in a Unit of Work.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. Associated projection class |
Guide: Query Handlers
Domain.application_service
Orchestrates use cases by coordinating aggregates, repositories, and domain
services. Uses @use_case for automatic Unit of Work wrapping.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. Associated aggregate class |
Guide: Application Services
Read Models
Domain.projection
A denormalized, query-optimized read model used on the read side of CQRS.
Projections take simple field types and ValueObject fields. Association
fields (Reference, HasOne, HasMany) are not allowed.
| Option | Default | Description |
|---|---|---|
abstract |
False |
Cannot be instantiated when True |
provider |
"default" |
Database provider name |
cache |
None |
Cache provider (takes precedence over provider) |
schema_name |
snake_case(cls) |
Table or collection name |
database_model |
None |
Custom database model class |
indexes |
() |
List of Index declarations for the persistence layer |
order_by |
() |
Default field ordering |
limit |
100 |
Default query result limit |
externally_populated |
False |
The projection is written by something outside this domain, so Protean does not require a projector for it and protean check stops reporting one as missing |
Guide: Projections · Declaring Indexes
Domain.projector
Maintains projections by reacting to domain events. Uses
@handle(EventClass) to process events. Unlike event handlers, projectors
explicitly target a projection and can listen to multiple stream categories.
| Option | Default | Description |
|---|---|---|
projector_for |
None |
Target projection class |
aggregates |
[] |
Aggregate classes whose events to consume |
stream_categories |
derived from aggregates |
Stream categories to subscribe to |
subscription_type |
None |
Subscription behavior enum |
subscription_profile |
None |
Subscription profile enum |
subscription_config |
{} |
Custom subscription configuration |
idempotent |
False |
Track processed event IDs so a redelivered event is applied once. Costs a lookup and a write per event; leave it off when the projection's writes are naturally idempotent (a full overwrite) rather than incremental (a counter) |
retries |
None |
Attempts before the message goes to the DLQ. Falls back to the subscription's max_retries |
backoff |
None |
Delay between retries, in seconds. Doubles per attempt |
retry_exceptions |
None |
Exception types worth retrying. Anything else fails straight to the DLQ |
Guide: Projectors
Domain.process_manager
A long-running coordinator that reacts to events across multiple aggregates, maintaining its own state to orchestrate multi-step workflows.
| Option | Default | Description |
|---|---|---|
abstract |
False |
Cannot be instantiated when True |
auto_add_id_field |
True |
Auto-adds an id identity field |
stream_category |
snake_case(cls) |
Message stream category |
aggregates |
[] |
Aggregate classes whose events to consume |
stream_categories |
derived from aggregates |
Stream categories to subscribe to |
subscription_type |
None |
Subscription behavior enum |
subscription_profile |
None |
Subscription profile enum |
subscription_config |
{} |
Custom subscription configuration |
sequential_by |
None |
Field name whose value partitions the stream, so events sharing a value are processed one at a time. See Sequential processing |
Guide: Process Managers
External Integration
Domain.subscriber
Consumes messages from external message brokers. Subscribers act as an anti-corruption layer, translating external payloads into domain operations.
| Option | Default | Description |
|---|---|---|
stream |
— | Required. Broker stream name to subscribe to |
broker |
"default" |
Broker provider name |
Guide: Subscribers
Persistence
Domain.repository
Persistence abstraction for aggregates. Protean provides a default repository automatically; custom repositories add domain-specific queries.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. Aggregate class being persisted |
database |
"ALL" |
Target database provider(s) |
Guide: Repositories
Domain.database_model
Maps a domain element to a specific database schema. Used to customize how aggregates or entities are stored.
| Option | Default | Description |
|---|---|---|
part_of |
— | Required. Associated aggregate or entity |
database |
None |
Database type (e.g. "SQLALCHEMY") |
schema_name |
from aggregate | Table or collection name |