Bounded Context¶
A Bounded Context is a boundary within which a particular domain model is defined and consistent. It organizes domain objects into logical groups and enforces type constraints at construction time.
Class Definition¶
from collections.abc import Iterable
from aod.domain import RootEntity, Service
from aod.schema import BoundedContext
class User(RootEntity):
id: str
name: str
class UserService(Service):
def get_user(self, user_id: str) -> User:
return User(id=user_id)
context = BoundedContext(
aggregate_roots=[User],
services=[UserService],
name="users",
)
Constructor Parameters¶
class BoundedContext:
def __init__(
self,
aggregate_roots: Iterable[RootEntityType] | None = None,
services: Iterable[ServiceType] | None = None,
*,
name: str | None = None,
) -> None: ...
| Parameter | Type | Default | Description |
|---|---|---|---|
aggregate_roots |
Iterable[RootEntityType] \| None |
None |
RootEntity subclasses that serve as aggregate roots in this context. Each must be a RootEntity subclass. Automatically discovers child entities and value objects through field type inspection |
services |
Iterable[ServiceType] \| None |
None |
Service subclasses that operate on this domain. Each must be a Service subclass. Method parameter and return types are validated for DDD compliance |
name |
str \| None |
None |
Optional human-readable name for the bounded context. Used in error messages and the describe() output |
aggregate_roots¶
Type: Iterable[RootEntityType] | None
The aggregate roots define the entry points to the domain. The bounded context inspects all field types on each aggregate root (recursively) to discover child entities and value objects.
class Address(ValueObject):
street: str
city: str
class User(RootEntity):
id: str
name: str
address: Address
context = BoundedContext(aggregate_roots=[User])
# Automatically discovers: Address (ValueObject)
Validation:
- Each item must be a class (not an instance) — raises
ClassExpectedErrorotherwise - Each item must be a subclass of
Entity— raisesInvalidEntityTypeErrorotherwise - Each item must be a subclass of
RootEntity— raisesInvalidRootEntityTypeErrorotherwise
services¶
Type: Iterable[ServiceType] | None
Services registered in a bounded context have their public method signatures validated:
- Parameters and return types must not be non-root
Entitysubclasses - Violations raise
InvalidServiceParameterError
class TaxCalculator(Service):
def calculate(self, amount: float, rate: float) -> float:
return amount * rate
context = BoundedContext(
aggregate_roots=[Order],
services=[TaxCalculator],
name="billing",
)
name¶
Type: str | None
An optional label for the context. Used in:
__repr__()— returns the name if setDuplicateDomainTypeErrormessages — identifies which context a type already belongs todescribe()output — keys are context names
Instance Attributes¶
After construction, a BoundedContext exposes these read-only attributes:
| Attribute | Type | Description |
|---|---|---|
aggregate_roots |
tuple[RootEntityType, ...] |
Tuple of registered aggregate root classes |
services |
tuple[ServiceType, ...] |
Tuple of registered service classes |
entities |
tuple[EntityType, ...] |
Tuple of all discovered entities (non-root) from aggregate root field inspection |
value_objects |
tuple[ValueObjectType, ...] |
Tuple of all discovered value objects from recursive field inspection |
name |
str \| None |
Optional context name |
Discovery Process¶
When constructed, BoundedContext recursively discovers all domain types referenced by the aggregate roots:
- Type extraction: For each aggregate root, all field types are extracted from type annotations
- Recursive traversal: For each discovered Entity or ValueObject type, its fields are also inspected
- Type categorisation: Discovered types are split into entities and value objects and stored in
self.entitiesandself.value_objects
class ProductId(ValueObject):
value: str
class OrderLine(Entity):
product_id: ProductId
quantity: int
class Order(RootEntity):
id: str
lines: list[OrderLine]
context = BoundedContext(aggregate_roots=[Order])
# Discovered: OrderLine (Entity), ProductId (ValueObject)
Type Constraints Enforced¶
Root Entity Nesting¶
No RootEntity can be nested inside another Entity:
class User(RootEntity):
id: str
class Order(RootEntity):
id: str
user: User # InvalidNestedTypeError!
Instead, reference by ID:
Value Object Constraints¶
ValueObjects can only contain primitives or other ValueObjects:
class Order(Entity):
id: str
class OrderLine(ValueObject):
order: Order # InvalidNestedTypeError! Entity not allowed in VO
Service Method Constraints¶
Service methods cannot accept or return non-root Entity subclasses:
class User(Entity):
id: str
class UserService(Service):
def get_user(self, user_id: str) -> User: # InvalidServiceParameterError!
pass
Allowed: RootEntity, ValueObject, custom classes, primitives.
Duplicate Detection¶
BoundedContext itself does not detect duplicates globally. Duplicate detection happens at the App level when multiple contexts are composed:
from aod.schema import App, Module, Infrastructure
context1 = BoundedContext(aggregate_roots=[User], name="users")
context2 = BoundedContext(aggregate_roots=[User], name="admin")
mod1 = Module(name="users", context=context1, infrastructure=Infrastructure())
mod2 = Module(name="admin", context=context2, infrastructure=Infrastructure())
app = App("myapp", modules=[mod1, mod2]) # DuplicateDomainTypeError!
describe() Method¶
Returns a list of TypeDoc objects describing every type in the context, categorised by role (RootEntity, Entity, ValueObject, Service). Each TypeDoc includes field names, types, and defaults.
| Parameter | Type | Description |
|---|---|---|
| (none) | — | — |
Returns list[TypeDoc] — documentation entries for all domain types in this context.
__repr__() Method¶
Returns self.name if set, otherwise the default class representation.
App Composition¶
App composes multiple BoundedContext instances and enforces global duplicate detection. Import from aod.schema:
from aod.schema import App, Module, Infrastructure
class Product(RootEntity):
id: str
class Order(RootEntity):
id: str
product_context = BoundedContext(aggregate_roots=[Product], name="products")
order_context = BoundedContext(aggregate_roots=[Order], name="orders")
product_module = Module(name="products", context=product_context, infrastructure=Infrastructure())
order_module = Module(name="orders", context=order_context, infrastructure=Infrastructure())
app = App("ecommerce", modules=[product_module, order_module])
App.__init__() Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
— | Application name |
modules |
Iterable[Module] |
— | One or more modules, each wrapping a bounded context with infrastructure |
description |
str |
"" |
Optional description of the application |
Raises DuplicateDomainTypeError if any domain type appears in more than one context across modules.
Common Patterns¶
E-Commerce¶
from aod.schema import BoundedContext
class Product(RootEntity):
id: str
name: str
price: float
class Order(RootEntity):
id: str
product_id: str
quantity: int
product_context = BoundedContext(
aggregate_roots=[Product],
name="products",
)
order_context = BoundedContext(
aggregate_roots=[Order],
name="orders",
)
User Management¶
class User(RootEntity):
id: str
email: str
name: str
class Role(ValueObject):
name: str
permissions: list[str]
class UserService(Service):
def assign_role(self, user: User, role: str) -> None: ...
user_context = BoundedContext(
aggregate_roots=[User],
services=[UserService],
name="users",
)
Exceptions¶
| Exception | Raised When |
|---|---|
InvalidEntityTypeError |
An aggregate root is not a subclass of Entity |
InvalidRootEntityTypeError |
An aggregate root is an Entity but not a RootEntity |
InvalidServiceTypeError |
A service is not a subclass of Service |
ClassExpectedError |
A class is expected but an instance was provided |
InvalidNestedTypeError |
An Entity or ValueObject field references a RootEntity |
InvalidServiceParameterError |
A service method uses a non-root Entity as parameter/return type |
DuplicateDomainTypeError |
Same domain type registered in multiple contexts (raised by App) |
Next Steps¶
Entity & RootEntity
Learn about aggregate roots
ValueObject
Learn about value objects
Service
Learn about services
Event System
Learn about domain events