Skip to main content

Generated Project Architecture

Castlecraft Architect scaffolds projects with a well-defined layered architecture, rooted in Domain-Driven Design (DDD) principles. This structure promotes separation of concerns, modularity, and maintainability. Understanding this architecture is key to effectively building upon the generated boilerplate.

The top-level structure of a generated project (let's call the main application package app_root_name) typically looks like this:

project_root/
├── alembic.example.ini
├── app_root_name/
│ ├── api/
│ ├── application/
│ ├── domain/
│ ├── infrastructure/
│ ├── config.py
│ ├── di.py
│ ├── main.py
│ └── __init__.py
├── logging.example.ini
├── migrations/
├── pyproject.toml
├── README.md
├── scripts/
└── tests/

Let's dive into each layer within the app_root_name directory:

1. API Layer (app_root_name/api/)

This layer is responsible for exposing your application's functionality to the outside world, typically via HTTP endpoints.

  • deps.py: Contains FastAPI dependencies, such as get_current_user for authentication and get_async_session for database sessions.
  • exceptions.py: Defines custom exceptions specific to the API layer.
  • v1/ (and other versions like v2/, internal/): Versioned API directories. Each version contains:
    • dtos/: Data Transfer Objects (DTOs) used for API request and response payloads. Architect generates DTO files (e.g., ${dto_name}.py) here.
    • routers/: FastAPI routers defining API endpoints. Architect generates router files (e.g., ${router_name}.py) here.

2. Application Layer (app_root_name/application/)

The application layer orchestrates domain logic and use cases. It acts as a bridge between the API layer and the Domain layer. It does not contain business logic itself but coordinates domain objects to perform tasks.

  • exceptions.py: Custom exceptions relevant to application service operations.
  • ${bounded_ctx}/: Directories representing different Bounded Contexts within your application. Each bounded context directory contains:
    • commands/: Definitions of command objects (e.g., CreateOrderCommand). Architect generates command files (e.g., ${command_name}.py).
    • queries/: Definitions of query objects (e.g., GetOrderByIdQuery). Architect generates query files (e.g., ${query_name}.py).
    • handlers/: Implementations for command, query, and event handlers.
      • Command Handlers (e.g., ${command_handler_name}.py)
      • Query Handlers (e.g., ${query_handler_name}.py)
      • Event Handlers (e.g., ${event_handler_name}.py)
      • Permissions related to handlers (e.g., ${handler_permission_name}.py) are also scaffolded here.
    • projectors/: Application-level projectors (e.g., ${application_projector_name}.py) that might update read models or perform other tasks in response to domain events, often interacting with infrastructure.
    • di.py: Dependency Injection setup specific to this bounded context's application services and handlers.

3. Domain Layer (app_root_name/domain/)

This is the heart of your application, containing the business logic, entities, value objects, domain services, and domain events. It should be independent of other layers like API and Infrastructure.

  • shared_kernel/: Contains domain concepts that are shared across multiple Bounded Contexts. Its structure mirrors that of a bounded context (aggregates, events, services, etc.), but these components are designed for reuse.
    • aggregates/, event_sourced_aggregates/, events/, services/, value_objects/, specifications/, projections/, sagas/, factories/, policies/, repositories/
    • exceptions.py: Shared domain exceptions.
    • di.py: DI setup for shared kernel components.
  • ${bounded_ctx}/: Directories for each Bounded Context. This is where the core domain logic for that specific part of the business resides.
    • aggregates/: Domain Aggregates (e.g., ${domain_aggregate_name}.py).
    • event_sourced_aggregates/: Event-Sourced Aggregates (e.g., ${domain_event_sourced_aggregate_name}.py).
    • events/: Domain Events (e.g., ${domain_event_name}.py).
    • services/: Domain Services (e.g., ${domain_service_name}.py).
    • value_objects/: Value Objects (e.g., ${domain_value_object_name}.py).
    • specifications/: Domain Specifications (e.g., ${domain_specification_name}.py).
    • projections/: Domain Projections (e.g., ${domain_projection_name}.py), representing read models derived from domain state.
    • sagas/: Saga orchestrators and state definitions (e.g., ${saga_orchestrator_name}.py, ${saga_state_name}.py).
    • factories/: Factories for creating complex domain objects (e.g., ${factory_name}.py).
    • policies/: Domain Business Policies (e.g., ${domain_business_policy_name}.py).
    • repositories/: Abstract repository interfaces for domain and event-sourced aggregates (e.g., ${domain_repository_name}.py, ${domain_event_sourced_repository_name}.py).
    • exceptions.py: Bounded context-specific domain exceptions.
    • di.py: DI setup for this bounded context's domain components.

4. Infrastructure Layer (app_root_name/infrastructure/)

This layer deals with external concerns like databases, messaging queues, third-party APIs, and other technical details. It provides concrete implementations for interfaces defined in the domain or application layers (e.g., repositories).

  • di.py: Dependency Injection setup for infrastructure components.
  • exceptions.py: Infrastructure-specific exceptions.
  • messaging/: Components related to message handling.
    • Event Consumers (e.g., ${event_consumer_name}.py)
    • Event Publishers (e.g., ${event_publisher_name}.py)
  • event_stores/: Concrete implementations for event stores (e.g., ${infrastructure_event_store_name}.py).
  • snapshot_stores/: Concrete implementations for snapshot stores (e.g., ${infrastructure_snapshot_store_name}.py).
  • projection_stores/: Concrete implementations for projection stores (e.g., ${infrastructure_projection_store_name}.py).
  • persistence/: Deals with data storage.
    • ${bounded_ctx}/: Persistence concerns specific to a Bounded Context.
      • models/: Database models/schemas (e.g., SQLAlchemy models - ${model_name}.py).
      • repositories/: Concrete repository implementations (e.g., ${infrastructure_repository_name}.py), including those for saga state (${saga_state_model_name}.py, ${saga_state_repository_name}.py).
      • anti_corruption/: For implementing Anti-Corruption Layers (ACLs).
        • translators/: Translators for ACLs (e.g., ${anti_corruption_translator_name}.py).
  • ${infrastructure_module_name}/: Custom infrastructure modules defined by the user. This is where you'd place code for interacting with external services, custom gateways, LLM integrations, or a concrete AuthorizationService implementation. Architect can scaffold infrastructure services (e.g., ${infrastructure_service_name}.py) here.

Core Application Files (app_root_name/)

  • config.py: Application settings management, typically using Pydantic Settings.
  • di.py: The main Dependency Injection setup for the application, orchestrating the registration of components from all layers.
  • main.py: The FastAPI application entry point, where the FastAPI app is created, routers are included, and middleware is configured.

Other Project Directories

  • migrations/: Database migration scripts (typically Alembic).
  • scripts/: Utility scripts for development or deployment.
  • tests/: Contains unit, integration, and end-to-end tests, often mirroring the app_root_name structure.

Key Principles

  • Dependency Rule: Dependencies flow inwards. The API layer depends on the Application layer, which depends on the Domain layer. The Infrastructure layer implements interfaces defined in the Domain or Application layers and can be depended upon by the Application layer (via interfaces) or used directly by the API layer for specific infrastructure concerns. The Domain layer should have no knowledge of outer layers.
  • Bounded Contexts: Logic is organized into Bounded Contexts to manage complexity and ensure clear boundaries between different parts of the domain.
  • Shared Kernel: Common domain concepts are placed in the shared_kernel to avoid duplication while maintaining domain integrity.
  • Dependency Injection: Used throughout to manage dependencies and promote loose coupling. Each layer and bounded context can have its own di.py for registering its specific components.

This layered and modular structure, scaffolded by Castlecraft Architect, provides a robust foundation for building scalable and maintainable DDD applications. While Architect provides this structure, developers have the flexibility to adapt and organize their code further as needed, adhering to best practices or specific project requirements.