Domain-Driven Design (DDD) Primitives
Relevant source files
The following files were used as context for generating this wiki page:
Forze is an infrastructure toolkit designed to facilitate the implementation of backend services using Domain-Driven Design (DDD) and Hexagonal Architecture. The library provides a set of reusable primitives and structural patterns that enforce a strict separation between the domain, application, and infrastructure layers. By utilizing well-defined ports and adapters, Forze ensures that the core business logic remains isolated from external technical concerns.
The framework's DDD primitives focus on providing explicit boundaries for domain entities (Documents), commands, and read models. These primitives enable developers to build systems that are highly testable, composable, and framework-agnostic, where dependencies flow inward toward the pure domain logic.
Sources: README.md, skills/forze-framework-usage/SKILL.md:12-25
Document Aggregate Structure
In Forze, the primary unit of domain logic is the Document aggregate. A standard aggregate implementation typically involves four distinct model types to handle different aspects of the entity's lifecycle and data access patterns.
Model Classifications
| Type | Base Class | Purpose |
|---|---|---|
| Domain Model | Document | Entity containing business logic, validation, and state. |
| Create Command | CreateDocumentCmd | Input DTO used specifically for the creation of a new document. |
| Update Command | BaseDTO | A partial update payload used for patching existing documents. |
| Read Model | ReadDocument | A frozen, read-only projection optimized for queries. |
Sources: skills/forze-domain-aggregates/SKILL.md:15-28
Core Document Fields
All primitives based on Document or ReadDocument inherit a standard set of metadata fields required for tracking and optimistic concurrency:
id: The unique identifier for the document.rev: The revision number, used to enforce optimistic concurrency during writes.created_at: Timestamp indicating when the entity was first persisted.last_update_at: Timestamp of the most recent modification.
Sources: skills/forze-domain-aggregates/SKILL.md:46-48, skills/forze-documents-search/SKILL.md:18-20
Logical Specifications (Specs)
Forze distinguishes between logical requirements and physical implementation through the use of "Specs." A Spec defines the metadata, models, and logical naming of a resource without binding it to a specific database table or external service.
DocumentSpec
A DocumentSpec binds the domain, command, and read models together under a unique logical name. This name (often defined as a StrEnum) is used by the execution context to route operations to the correct infrastructure adapters.
The write dictionary in a DocumentSpec typically contains references to the domain model class, the create_cmd class, and optionally an update_cmd class.
Sources: skills/forze-domain-aggregates/SKILL.md:72-104, skills/forze-specs-infrastructure/SKILL.md:16-25
Domain Validation and Invariants
Business rules are enforced within the domain model using update validators. These validators are triggered during the Document.update() process to ensure that state transitions and field modifications adhere to domain invariants.
from forze.domain.validation import update_validator
from forze.base.exceptions import exc
class Project(Document):
status: str = "draft"
@update_validator(fields={"status"})
def _validate_transition(before, after, diff):
allowed = {"draft": {"active"}, "active": {"archived"}}
if after.status not in allowed.get(before.status, set()):
raise exc.validation("Invalid status transition.")
Sources: skills/forze-domain-aggregates/SKILL.md:58-70
Hexagonal Ports
Application logic interacts with the domain through "Ports" (protocols). These ports are resolved at runtime from the ExecutionContext based on the logical specs provided. This ensures the application layer never imports infrastructure-specific adapter classes.
Primary Access Ports
- DocumentQueryPort: Used for fetching individual documents by ID or performing filtered searches/listings.
- DocumentCommandPort: Used for persisting changes, including creation, updates (with revision checks), and deletion.
- SearchQueryPort: Provides full-text search capabilities, often supporting weighted fields and filtered results.
Data Flow for Port Resolution
The following diagram illustrates how a handler obtains a port to interact with the domain aggregate without knowing the underlying database implementation.
Domain Mixins
Forze provides standard mixins to extend domain models with common behaviors without duplicating logic across different aggregates.
| Mixin | Functionality | Key Fields Added |
|---|---|---|
SoftDeletionMixin | Support for marking entities as deleted without removal. | is_deleted: bool |
NumberIdMixin | Adds human-readable incremental IDs. | number_id: int |
CreatorIdMixin | Audit support for tracking who created the entity. | creator_id: str |
MetadataMixin | Standardizes descriptive fields for entities. | name, display_name |
Sources: skills/forze-domain-aggregates/SKILL.md:40-45
Summary of Layered Boundaries
To maintain technical accuracy and project health, Forze strictly enforces the following architectural boundaries:
- Domain Layer: Pure business logic and models. Must not import ports, adapters, or application contracts.
- Application Layer: Contains handlers and contracts. Imports the domain layer but remains ignorant of specific infrastructure implementations.
- Infrastructure Layer: Implements the contracts (adapters). This is where SQL, NoSQL, and external API logic resides.
- Interface Layer: The entry point (e.g., FastAPI routes) that resolves handlers via a frozen registry.
Sources: skills/forze-framework-usage/SKILL.md:12-35, pyproject.toml:211-230