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
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.
Related Documents¶
- Event-Driven Mindset - How domain events enable loose coupling
- Cloud-Native Mindset - How Clean Architecture enables cloud-native
- AI-First Development - How Clean Architecture enables agent generation
- Microservice Template - Template that enforces Clean Architecture
- Microservice Standard Blueprint - Blueprint for generated services