Clean Architecture Guide
How the AI Accountant follows Clean Architecture (Robert C. Martin) with Event Sourcing.
The Dependency Rule
The most important rule: source code dependencies point inward only.
| Layer (outer → inner) | Depends on | Never depends on |
|---|---|---|
| Frameworks & Drivers | Everything | — |
| Interface Adapters | Use Cases, Entities | Frameworks |
| Use Cases | Entities, interfaces | Adapters, Frameworks |
| Entities | Nothing | Everything else |
Inner layers NEVER know about outer layers. Outer layers depend on inner layers, not the reverse.
## The Four Layers
### 1. Entities (Enterprise Business Rules)
The core domain. Pure business logic. No dependencies on anything external.
**Rules:**
- No I/O (no database, no HTTP, no file system)
- No framework dependencies
- Can be used by any application — not tied to web, CLI, or Discord
- Easily unit tested with plain data
**In our system:** `Engines/`
| Engine | What it knows | What it does NOT know |
|--------|--------------|----------------------|
| `ConfidenceEngine` | How to score a proposal against a pattern | Where patterns come from (DB? file? API?) |
| `RulesEngine` | Company policies (thresholds, VAT rates) | How policies are stored or loaded |
| `AmountEngine` | How to calculate VAT from gross/net | What currency or accounting system is used |
**Test:** Pass in data, get a result. No mocks needed.
```csharp
// Pure function — no I/O, no dependencies
var engine = new ConfidenceEngine();
var score = engine.Score(
new ProposalData("Adobe", 199, VatRate: 25),
new MerchantPattern("Adobe", "6540", 25, 1.0f)
);
Assert.True(score.Score >= 0.9f);
2. Use Cases (Application Business Rules)
Application-specific business rules. Orchestrate the flow of data to and from Entities.
Rules:
- May call Entities (Engines)
- Defines interfaces for what it needs from the outside (e.g., IEventStore, IPatternStore)
- Does NOT implement those interfaces — that's the outer layer's job
- Owns the transaction boundary
In our system: Managers/
| Manager | Orchestrates | Calls |
|---|---|---|
EventManager |
Propose → Score → Route | ConfidenceEngine, IEventStore, IPatternStore |
ReviewManager |
Approve/Reject → Execute | IEventStore, IFolioProvider, IFikenProvider |
Key: Managers define interfaces (IEventStore, IPatternStore), they don't know about Postgres or HTTP.
// Manager uses interfaces — doesn't know about Postgres
public class EventManager
{
private readonly IEventStore _eventStore; // interface, not implementation
private readonly IPatternStore _patternStore; // interface, not implementation
private readonly ConfidenceEngine _confidence; // entity, no interface needed
public async Task<ProposalResult> ProposeAsync(...)
{
var pattern = await _patternStore.GetAsync(merchant); // I/O via interface
var score = _confidence.Score(proposal, pattern); // pure logic
var evt = await _eventStore.AppendAsync(...); // I/O via interface
return result;
}
}
3. Interface Adapters
Convert data between the Use Cases and the outside world.
Rules: - Implement the interfaces defined by Use Cases - Convert external data formats to internal ones - No business logic — only data transformation and protocol handling
In our system: ResourceAccess/ (implements interfaces) + Clients/ (HTTP endpoints)
| Component | Adapts between |
|---|---|
EventStore |
IEventStore ↔ PostgreSQL |
PatternStore |
IPatternStore ↔ PostgreSQL |
FolioProvider |
IAccountingProvider ↔ Folio REST API |
FikenProvider |
IAccountingProvider ↔ Fiken REST API |
| HTTP Endpoints | HTTP requests ↔ Manager method calls |
4. Frameworks & Drivers
The outermost layer. Frameworks, tools, database drivers, web servers.
Rules: - Glue code only — no business logic - Where you configure DI, wire up routes, start the server - The most volatile layer — frameworks change, business rules don't
In our system: Program.cs (ASP.NET Core setup, DI registration, route mapping)
How It Maps to Our System
The Key Insight: Dependency Inversion
The EventManager needs to store events. But it doesn't know about Postgres.
Wrong (dependency points outward)
EventManager → EventStore → Postgres
Right (dependency points inward)
EventManager → IEventStore (interface, defined by Manager)
EventStore implements IEventStore (outer layer implements inner layer's interface)
The Manager defines what it needs (IEventStore). The outer layer provides it (EventStore backed by Postgres). If we swap Postgres for MongoDB tomorrow, the Manager doesn't change.
Event Sourcing in Clean Architecture
Event sourcing fits naturally: events are the lingua franca between layers.
Book-E → HTTP Endpoint → EventManager → ConfidenceEngine (pure logic)
→ IEventStore.AppendAsync (interface)
↓
EventStore (Postgres implementation)
↓
events table (immutable, append-only)
Every action is an event. State is derived from events. The Engines never touch the event store — they receive data and return decisions.
Project Structure
src/AccountingApi/
├── Program.cs ← Frameworks & Drivers (DI, routes, server)
│
├── Engines/ ← Entities (innermost, no dependencies)
│ ├── ConfidenceEngine.cs
│ ├── RulesEngine.cs
│ └── AmountEngine.cs
│
├── Managers/ ← Use Cases (orchestration via interfaces)
│ ├── EventManager.cs
│ └── ReviewManager.cs
│
├── ResourceAccess/ ← Interface Adapters (implement interfaces)
│ ├── IAccountingProvider.cs ← interface (defined for Use Cases)
│ ├── FolioProvider.cs ← implements IAccountingProvider
│ ├── FikenProvider.cs ← implements IAccountingProvider
│ ├── EventStore.cs ← implements IEventStore
│ ├── PatternStore.cs ← implements IPatternStore
│ └── AccountingProviderFactory.cs
│
└── appsettings.json ← Configuration (Frameworks & Drivers)
src/AccountingApi.Tests/
├── Engines/ ← Test Entities with plain data (no mocks)
│ ├── ConfidenceEngineTests.cs
│ ├── RulesEngineTests.cs
│ └── AmountEngineTests.cs
└── ...
What Each Layer Can Import
| Layer | Can use | Cannot use |
|---|---|---|
| Engines | Other Engines, standard library | Managers, ResourceAccess, frameworks |
| Managers | Engines, interfaces (IEventStore, etc.) | ResourceAccess implementations, frameworks |
| ResourceAccess | Interfaces it implements, external SDKs | Engines, Managers |
| Program.cs | Everything (wires it all together) | — |
Testing Strategy
| Layer | Test type | Mocks needed |
|---|---|---|
| Engines | Unit tests | None — pure data in, data out |
| Managers | Unit tests | Mock interfaces (IEventStore, IPatternStore) |
| ResourceAccess | Integration tests | Real Postgres / real API (or testcontainers) |
| Endpoints | API tests | Full stack with test database |
Comparison with Other Architectures
| Concept | Clean Architecture | Hexagonal (Ports & Adapters) | iDesign | DDD |
|---|---|---|---|---|
| Inner layer | Entities | Domain | Engines | Aggregates |
| Orchestration | Use Cases | Application Services | Managers | Application Layer |
| External I/O | Gateways | Adapters | Resource Access | Infrastructure |
| Entry points | Controllers | Ports | Clients | — |
| Key rule | Dependencies inward | Ports define contracts | No layer skipping | Ubiquitous language |
They're all variations of the same idea: protect the core business logic from external concerns.