Skip to content

Clean Architecture & DDD

This document explains how ConnectSoft uses Clean Architecture and Domain-Driven Design as automation enablers. It is written for architects and engineers building agent-generatable systems.

We use DDD and Clean Architecture not as "nice patterns", but as constraints that make agent automation safe. Every module has Domain / Application / Infrastructure / API / Tests structure, and each agent knows exactly which layer it may touch.

Important

Bounded contexts become units of generation, deployment, and ownership. This turns "write code somewhere" into "fill this well-defined slot in this template with these rules"—making agent generation predictable and safe.

Clean Architecture Layers

Clean Architecture organizes code into layers with clear dependencies:

flowchart TD
    subgraph "API Layer"
        API[Controllers<br/>Request/Response Models<br/>API Documentation]
    end

    subgraph "Application Layer"
        APP[Use Cases<br/>Workflows<br/>DTOs<br/>Application Services]
    end

    subgraph "Domain Layer"
        DOM[Entities<br/>Aggregates<br/>Value Objects<br/>Domain Services<br/>Domain Events]
    end

    subgraph "Infrastructure Layer"
        INFRA[Repositories<br/>External Services<br/>Messaging<br/>Persistence<br/>Configuration]
    end

    subgraph "Tests Layer"
        TESTS[Unit Tests<br/>Integration Tests<br/>Acceptance Tests]
    end

    API -->|Depends On| APP
    APP -->|Depends On| DOM
    INFRA -->|Depends On| DOM
    INFRA -->|Depends On| APP

    TESTS -.->|Tests| API
    TESTS -.->|Tests| APP
    TESTS -.->|Tests| DOM
    TESTS -.->|Tests| INFRA

    style DOM fill:#2563EB,color:#fff
    style APP fill:#4F46E5,color:#fff
    style INFRA fill:#6B7280,color:#fff
    style API fill:#10B981,color:#fff
Hold "Alt" / "Option" to enable pan & zoom

Dependency Rules

Dependencies flow inward: - Outer layers depend on inner layers - Inner layers never depend on outer layers - Domain layer has no dependencies (pure business logic)

Layer Responsibilities:

  • Domain Layer - Core business logic, entities, value objects, domain services (no external dependencies)
  • Application Layer - Use cases, workflows, orchestration, DTOs (depends only on Domain)
  • Infrastructure Layer - Persistence, external services, messaging, frameworks (depends on Domain and Application)
  • API Layer - Controllers, DTOs, request/response handling (depends on Application)
  • Tests Layer - Unit, integration, acceptance tests (can test any layer)

Important

Dependencies flow inward: outer layers depend on inner layers, but inner layers never depend on outer layers. This ensures that business logic remains independent of frameworks, databases, and external systems.

Domain-Driven Design in ConnectSoft

Domain-Driven Design provides the domain modeling concepts:

Bounded Contexts

Definition: Explicit boundaries within which a domain model applies.

At ConnectSoft: - Each bounded context = one microservice - Each bounded context has its own domain model - Bounded contexts communicate via events, not shared models

Example: Invoice bounded context has its own Invoice entity, separate from Billing bounded context's Invoice entity.

Aggregates

Definition: Clusters of entities treated as a unit for data changes.

At ConnectSoft: - Aggregates enforce consistency boundaries - One aggregate = one transaction boundary - Aggregates emit domain events

Example: InvoiceAggregate contains Invoice entity and related LineItems, all updated atomically.

Domain Events

Definition: Important business occurrences that other parts of the system need to know about.

At ConnectSoft: - Domain events represent business facts - Events are immutable and timestamped - Events enable eventual consistency between bounded contexts

Example: InvoiceCreated, PaymentProcessed, SubscriptionExpired

Ubiquitous Language

Definition: Common language used by all team members to connect all activities of the team with the software.

At ConnectSoft: - Domain terms used consistently in code, docs, and conversations - Code reflects domain language (entities, value objects named after domain concepts) - ADRs document domain language decisions

Aggregates, Entities, and Value Objects

Entities

Definition: Objects with identity that persist over time.

Characteristics: - Have a unique identifier (ID) - Can change state while maintaining identity - Compared by identity, not attributes

Example: Invoice entity has Id (Guid), Amount, Status; identity persists even if amount changes.

Value Objects

Definition: Immutable objects defined by their attributes.

Characteristics: - No identity (compared by value, not reference) - Immutable (cannot change after creation) - Self-validating (enforce invariants in constructor)

Example: Money value object with Amount and Currency; two Money objects with same amount and currency are equal.

Aggregates

Definition: Clusters of entities and value objects treated as a unit.

Characteristics: - One aggregate root (entity that controls access) - Consistency boundary (all changes within aggregate are atomic) - References to other aggregates by ID only (no direct object references)

Example: InvoiceAggregate with Invoice (root) and LineItem entities; all changes to invoice and line items are atomic.

How the Microservice Template Applies This

The Microservice Template enforces Clean Architecture and DDD:

Template Structure

Domain Layer ({ServiceName}.Domain): - Entities, aggregates, value objects - Domain services - Domain events - No external dependencies

Application Layer ({ServiceName}.Application): - Use cases - DTOs and mappers - Application services - Depends only on Domain

Infrastructure Layer ({ServiceName}.Infrastructure): - Repositories (data access) - External service clients - Messaging (event bus) - Depends on Domain and Application

API Layer ({ServiceName}.Api): - Controllers - Request/response models - API documentation - Depends on Application

Tests Layer ({ServiceName}.Tests): - Unit tests (Domain, Application) - Integration tests (API, Infrastructure) - Acceptance tests (end-to-end) - Can test any layer

Agent Constraints

Engineering Agents: - Generate Domain layer first (entities, value objects) - Generate Application layer (use cases, DTOs) - Generate Infrastructure layer (repositories, external services) - Generate API layer (controllers, request/response models)

QA Agents: - Generate unit tests for Domain and Application - Generate integration tests for API and Infrastructure - Validate layer dependencies (no violations)

Architect Agents: - Design bounded contexts - Define aggregates and entities - Design domain events - Ensure Clean Architecture compliance

Common Pitfalls and Anti-Patterns

Anemic Domain Model

Anti-Pattern: Entities are just data containers with no behavior.

Problem: Business logic leaks into application layer, domain model becomes meaningless.

Solution: Entities contain business logic, enforce invariants, and emit domain events.

Example (Bad):

// Anemic - just data
public class Invoice
{
    public Guid Id { get; set; }
    public decimal Amount { get; set; }
    public InvoiceStatus Status { get; set; }
}

// Business logic in application layer
public class InvoiceService
{
    public void ProcessPayment(Invoice invoice, decimal amount)
    {
        invoice.Amount -= amount; // Logic outside domain
        if (invoice.Amount == 0) invoice.Status = InvoiceStatus.Paid;
    }
}

Example (Good):

// Rich domain model
public class Invoice
{
    public Guid Id { get; private set; }
    public Money Amount { get; private set; }
    public InvoiceStatus Status { get; private set; }

    public void ProcessPayment(Money payment)
    {
        if (Status != InvoiceStatus.Pending)
            throw new InvalidOperationException("Can only pay pending invoices");

        Amount = Amount.Subtract(payment);
        if (Amount.IsZero)
        {
            Status = InvoiceStatus.Paid;
            RaiseDomainEvent(new InvoicePaidEvent(Id));
        }
    }
}

Leaking Infrastructure into Domain

Anti-Pattern: Domain layer depends on infrastructure (databases, frameworks, external services).

Problem: Domain logic becomes coupled to infrastructure, making it hard to test and change.

Solution: Domain layer has no infrastructure dependencies; use dependency inversion.

Example (Bad):

// Domain depends on infrastructure
public class InvoiceRepository
{
    public void Save(Invoice invoice)
    {
        using var db = new SqlConnection(...); // Infrastructure in domain
        db.Execute("INSERT INTO Invoices...");
    }
}

Example (Good):

// Domain defines interface
public interface IInvoiceRepository
{
    Task SaveAsync(Invoice invoice);
}

// Infrastructure implements interface
public class SqlInvoiceRepository : IInvoiceRepository
{
    public async Task SaveAsync(Invoice invoice)
    {
        // Infrastructure implementation
    }
}

Over-Sharing Entities Between Contexts

Anti-Pattern: Same entity used across multiple bounded contexts.

Problem: Coupling between contexts, shared database, can't evolve independently.

Solution: Each bounded context has its own domain model; share via events, not entities.

Example (Bad):

// Shared entity across contexts
public class User // Used by Identity, Billing, Notification contexts
{
    public Guid Id { get; set; }
    public string Email { get; set; }
    // All contexts depend on this
}

Example (Good):

// Each context has its own model
// Identity context
public class User { public Guid Id; public string Email; }

// Billing context
public class Customer { public Guid UserId; public BillingAddress Address; }

// Share via events
public class UserCreatedEvent { public Guid UserId; public string Email; }

Warning

Never: Create anemic domain models, leak infrastructure into domain, or share entities between bounded contexts. These anti-patterns break Clean Architecture and make agent generation unpredictable.