Skip to main content

DDD with Castlecraft Architect

Castlecraft Architect is fundamentally designed to support and promote Domain-Driven Design (DDD). While the Introduction touches upon this philosophy, this page delves deeper into how Architect translates core DDD concepts into a tangible project structure and development workflow.

Core DDD Principles Embodied by Architect

Architect helps you implement several key DDD patterns and principles through its component generation and project structuring:

1. Layered Architecture

Architect scaffolds projects into a classic layered architecture, promoting separation of concerns:

  • API/Presentation Layer (app_root_name/api/): Handles external interactions (e.g., HTTP requests), data serialization/deserialization (DTOs), and delegates to the Application Layer.
  • Application Layer (app_root_name/application/): Orchestrates use cases. It contains application services (implemented as Command and Query Handlers) that use domain objects to fulfill requests. It does not contain business logic itself.
  • Domain Layer (app_root_name/domain/): The heart of your software. It contains the business logic, including Aggregates, Entities, Value Objects, Domain Services, Domain Events, and Repository interfaces. This layer is kept pure and independent of infrastructure concerns.
  • Infrastructure Layer (app_root_name/infrastructure/): Provides technical capabilities like database access (implementing Repository interfaces), messaging, interaction with external services, etc.

This separation is crucial for maintainability and testability, ensuring that your core domain logic remains isolated from external and technical details. The Generated Project Architecture page details this structure.

2. Bounded Contexts

DDD emphasizes breaking down a large, complex domain into smaller, more manageable Bounded Contexts, each with its own specific model and ubiquitous language. Architect supports this by:

  • Directory Structure: Allowing the creation of subdirectories within the application/ and domain/ layers (e.g., app_root_name/domain/ordering/, app_root_name/application/ordering/). Each such subdirectory can represent a Bounded Context.
  • Component Scoping: When generating components, you can specify a Bounded Context, ensuring that domain objects and application services are organized within their respective contexts.
  • Shared Kernel: Architect also provides a domain/shared_kernel/ directory for domain concepts that are common across multiple Bounded Contexts.

3. Aggregates, Entities, and Value Objects

These are fundamental building blocks of a rich domain model:

  • Aggregates (domain_aggregate, shared_aggregate): Clusters of domain objects (Entities and Value Objects) that are treated as a single unit for data changes. Architect scaffolds Aggregate classes, often with a root entity. Event-Sourced Aggregates (domain_event_sourced_aggregate) are also supported.
  • Entities: Objects with a distinct identity that persists over time. While Architect doesn't have a separate "Entity" component type (as they are often the root of an Aggregate or part of its internal structure), the generated Aggregate boilerplate provides a place to define them.
  • Value Objects (domain_value_object, shared_value_object): Immutable objects defined by their attributes, used to measure or describe things. Architect helps scaffold these, encouraging their use for richer domain modeling.

4. Domain Services (domain_service, shared_service)

When an operation doesn't naturally belong to an Aggregate or Value Object, it can be implemented as a Domain Service. Architect allows you to generate stubs for these services within the appropriate Bounded Context or Shared Kernel.

5. Repositories (domain_repository, infrastructure_repository)

Repositories provide an abstraction over data persistence, allowing the domain layer to remain ignorant of database details.

  • Architect scaffolds concrete Domain Repositories (e.g., OrderAggregateRepository) in the Domain Layer. These repositories are responsible for the lifecycle of their specific Aggregate (e.g., finding, saving). They encapsulate domain-specific persistence logic for the aggregate.
  • These Domain Repositories typically depend on a Unit of Work abstraction (like an SQLAlchemy AsyncSession) provided by the Infrastructure Layer for the actual data storage operations. This keeps the domain repository focused on the aggregate's consistency and lifecycle, while the infrastructure handles the specific database interactions.
  • The Infrastructure Layer also hosts infrastructure_repository components. These might be used for more specialized data access needs, read-model queries that don't go through an aggregate, or if you choose to implement very generic data access interfaces that your domain repositories might consume (though direct use of a session by domain repositories is common).

This adheres to the Dependency Inversion Principle, where the Domain Layer defines the contract, and the Infrastructure Layer provides the implementation.

6. Domain Events (domain_event, shared_event)

Domain Events represent significant occurrences within the domain. Architect helps generate Domain Event classes, encouraging an event-driven approach where appropriate. These events can be used for:

  • Communication between Aggregates (potentially across Bounded Contexts via integration events).
  • Triggering side effects (handled by Event Handlers in the Application Layer).
  • Building an audit log.
  • Event Sourcing.

7. Application Services (Commands & Queries - CQRS)

Architect promotes Command Query Responsibility Segregation (CQRS) at the application service level:

  • Commands (command) & Command Handlers (command_handler): Represent operations that change the state of the system.
  • Queries (query) & Query Handlers (query_handler): Represent operations that retrieve data without side effects.

This separation leads to clearer intent, more focused services, and can enable optimization of read and write paths independently.

8. Factories (factory)

For complex object creation that doesn't fit neatly within an Aggregate's constructor or a simple Domain Service, Architect supports the generation of Factory components.

9. Anti-Corruption Layers (ACLs) (anti_corruption_translator)

When integrating with external systems or legacy components that have different models, an ACL is crucial. Architect provides a anti_corruption_translator component type to help scaffold the translators that mediate between your Bounded Context's model and the external model.

The Role of Ubiquitous Language

While Architect is a code generation tool, its emphasis on clearly named components (Aggregates, Events, Services named after domain concepts) naturally encourages the development and use of a Ubiquitous Language – a shared language between domain experts and developers. Using consistent terminology in your component names, DTOs, and method signatures reinforces this shared understanding.

How Architect Facilitates DDD

  • Reduces Boilerplate: Architect automates the creation of the structural code for these DDD patterns, allowing developers to focus on the core business logic.
  • Provides Structure: It offers a consistent and opinionated project structure that aligns well with DDD principles, guiding teams in organizing their code.
  • Encourages Explicit Modeling: By requiring developers to think in terms of specific component types (Aggregate, Domain Event, etc.), Architect encourages more explicit and thoughtful domain modeling.
  • Supports Evolution: The Revision System allows the domain model and architecture to evolve in a controlled and trackable manner.
  • Lowers Barrier to Entry: For teams new to DDD, Architect provides a practical starting point and a "guardrail" for applying these patterns.
  • LLM Synergy: The structured output and context generation capabilities of Architect (see Leveraging LLMs) can be used to feed LLMs, which can then assist in further domain modeling discussions, code generation within the DDD patterns, and understanding the existing model.

Conclusion

Castlecraft Architect is more than just a code generator; it's a toolkit designed to help developers build robust, scalable, and maintainable applications by embracing the principles of Domain-Driven Design. By providing a clear structure, automating boilerplate, and encouraging explicit modeling, Architect empowers teams to focus on what truly matters: understanding and solving complex business problems through software.

To see these concepts in the context of a generated project, refer to the Generated Project Architecture documentation.