Skip to content

Contracts

Contracts are immutable data classes for commands (writes) and queries (reads). They define the typed interface for application operations and enforce constraints at class creation time.

Import

from aod.application import Command, Query

Class Reference

Command[TEntity, TResult]

Represents a request to change state. Always immutable.

Type Parameters:

Parameter Constraint Description
TEntity Must be a RootEntity subclass The root entity this command operates on
TResult Any type The return type of the command handler

Constructor: All declared fields become keyword constructor parameters (auto-generated by Pydantic).

Validation at class creation:

  • TEntity must be a RootEntity subclass — raises InvalidRootEntityTypeError otherwise
  • Field types must not reference non-root Entity classes — raises InvalidCommandFieldTypeError otherwise
  • Even nested references (e.g., list[Entity], Optional[Entity]) are checked
from aod.application import Command

class CreateUser(Command[User, None]):
    user_id: str
    name: str
    email: str

# Constructor takes all fields as keyword arguments
cmd = CreateUser(user_id="1", name="Alice", email="alice@example.com")

# Immutable — any mutation raises MutationForbiddenException
cmd.name = "Bob"  # MutationForbiddenException!

Parameters (constructor):

Parameter Type Description
*fields As declared Every declared field becomes a required or optional keyword argument
(default values) As declared Fields with = value are optional in the constructor

Query[TEntity, TResult]

Represents a request to read state. Always immutable.

Type Parameters:

Parameter Constraint Description
TEntity Must be a RootEntity subclass The root entity this query reads from
TResult Must contain at least one RootEntity The return type, validated to include a RootEntity

Validation at class creation:

  • TEntity must be a RootEntity subclass — raises InvalidRootEntityTypeError otherwise
  • TResult must contain at least one RootEntity type — raises InvalidQueryResultTypeError otherwise
  • Field types must not reference non-root Entity classes — raises InvalidCommandFieldTypeError otherwise
from aod.application import Query

class GetUser(Query[User, User | None]):
    user_id: str

query = GetUser(user_id="1")

query.user_id = "2"  # MutationForbiddenException!

Parameters (constructor):

Parameter Type Description
*fields As declared Every declared field becomes a required or optional keyword argument
(default values) As declared Fields with = value are optional in the constructor

Type Parameter Rules

TEntity: Must be RootEntity

Both Command and Query require TEntity to be a RootEntity subclass:

class User(RootEntity):
    id: str
    name: str

# Valid
class CreateUser(Command[User, None]):
    user_id: str

class NonRootEntity(Entity):
    id: str

# Invalid — NonRootEntity is not a RootEntity
class BadCommand(Command[NonRootEntity, None]):  # InvalidRootEntityTypeError!
    pass

TResult: Must contain RootEntity (Query only)

Query requires TResult to include at least one RootEntity type:

# Valid — User is a RootEntity
class GetUser(Query[User, User | None]):
    user_id: str

class ListUsers(Query[User, list[User]]):
    pass

class GetUserWithStats(Query[User, tuple[User, int]]):
    user_id: str

# Invalid — int does not contain a RootEntity
class BadQuery(Query[User, int]):  # InvalidQueryResultTypeError!
    pass

Field Validation

Contract fields are validated at class creation. Any field type that references a non-root Entity (even nested in generics) raises InvalidCommandFieldTypeError:

# Valid — str is not an Entity
class CreateUser(Command[User, None]):
    user_id: str
    name: str

# Invalid — User is a non-root Entity in the field
class CreateUser(Command[User, None]):
    user: User  # InvalidCommandFieldTypeError!

# Also invalid — Entity nested in generics
class CreateOrder(Command[Order, None]):
    items: list[OrderItem]  # InvalidCommandFieldTypeError! (if OrderItem is non-root Entity)

Complex Result Types

Queries support complex return types as long as at least one RootEntity is present:

# Tuple result
class GetUserWithStats(Query[User, tuple[User, int]]):
    user_id: str

# Optional result
class FindUser(Query[User, User | None]):
    email: str

# List result
class SearchUsers(Query[User, list[User]]):
    query: str
    limit: int = 10

# Union with RootEntity
class FindUserOrOrder(Query[User, User | Order]):
    identifier: str

Immutability

Both Command and Query are always immutable. Any attempt to modify a field raises MutationForbiddenException:

cmd = CreateUser(user_id="1", name="Alice", email="alice@example.com")
cmd.name = "Bob"  # MutationForbiddenException!

query = GetUser(user_id="1")
query.user_id = "2"  # MutationForbiddenException!

Testing

Use the build() helper to construct contracts without validation:

from aod.testing import build

# Build without validation — useful for test setup
cmd = build(CreateUser, user_id="1", name="Alice", email="alice@example.com")

# Or construct normally — validates all constraints
cmd = CreateUser(user_id="1", name="Alice", email="alice@example.com")
assert cmd.user_id == "1"

Common Patterns

CRUD Commands

class CreateUser(Command[User, None]):
    user_id: str
    name: str
    email: str

class UpdateUser(Command[User, None]):
    user_id: str
    name: str | None = None
    email: str | None = None

class DeleteUser(Command[User, None]):
    user_id: str

CRUD Queries

class GetUser(Query[User, User | None]):
    user_id: str

class ListUsers(Query[User, list[User]]):
    pass

class SearchUsers(Query[User, list[User]]):
    query: str
    limit: int = 10

Domain-Specific Commands

class PlaceOrder(Command[Order, None]):
    order_id: str
    items: list[OrderItem]

class ShipOrder(Command[Order, None]):
    order_id: str
    tracking_number: str

class CancelOrder(Command[Order, None]):
    order_id: str
    reason: str

Next Steps

UseCase

Learn how use cases handle contracts

Handlers

Learn about command/query handlers

Port

Learn about ports

Container

Learn about handler registration