Domain Layer¶
The domain layer is the heart of your application. It contains the business logic, rules, and data structures that define your domain.
Building Blocks¶
| Block | Description | Mutability |
|---|---|---|
| Entity | Mutable object with identity | Mutable |
| RootEntity | Aggregate root entity | Mutable |
| Identity Field | Identity via Field(id=True) |
- |
| ValueObject | Immutable, identity-less object | Immutable |
| Service | Stateless domain operation | Stateless |
| Event | Record of something that happened | Immutable |
| Invariants | Business rules enforced at construction (@field_invariance, @invariance) |
- |
Imports¶
from aod.domain import (
Entity,
RootEntity,
ValueObject,
Service,
Field,
PrivateField,
BoundedContext,
DomainException,
)
from aod.events import Event, EventCollector
Quick Example¶
from aod.domain import RootEntity, ValueObject, Field
from aod.events import Event
# Value Object — immutable
class Money(ValueObject):
amount: float
currency: str
# Event — immutable, auto-timestamped
class OrderPlaced(Event):
order_id: int
total: float
# Root Entity — mutable, has identity
class Order(RootEntity):
id: int = Field(id=True)
total: Money
def place(self) -> None:
self._event_emitter.emit(
OrderPlaced(order_id=self.id, total=self.total.amount)
)
Key Concepts¶
Mutation Guards¶
Entities and Root Entities have automatic mutation guards:
- Inside methods: Mutation is allowed (PASS state)
- Outside methods: Mutation is blocked (BLOCK state)
- During
__init__: Mutation is allowed (INHERIT state)
class User(RootEntity):
id: int = Field(id=True)
name: str
def rename(self, new_name: str) -> None:
self.name = new_name # Works!
user = User(id=1, name="Alice")
user.name = "Bob" # MutationForbiddenException!
Immutable Proxies¶
When you read attributes outside a mutation context, you get immutable proxies:
Business Invariants¶
Enforce domain rules at construction time with @field_invariance (field-level) and @invariance (model-level). Violations raise InvarianceException, a domain exception.
from aod.domain.validation import field_invariance
class Money(ValueObject):
amount: float
currency: str
@field_invariance("amount")
def amount_must_be_positive(cls, v: float) -> float:
if v < 0:
raise ValueError("Amount must be positive")
return v
Money(amount=-5.0, currency="USD") # InvarianceException!
Event Collection¶
Events are automatically collected across aggregate boundaries:
from aod.events import EventCollector
with EventCollector() as events:
order.place()
order.ship()
# events contains OrderPlaced and OrderShipped
Next Steps¶
Entity & RootEntity
Learn about mutable domain objects
ValueObject
Learn about immutable domain objects
Service
Learn about stateless domain operations
Event System
Learn about domain events
Bounded Context
Learn about organizing your domain
Invariants
Learn about business rules and validation