Skip to content

Architecture Principles

Rules that guide every design decision. When in doubt, refer here.

1. Dependencies Point Inward

The most important rule from Clean Architecture. Inner layers never know about outer layers.

Layer (inner → outer) Knows about Does NOT know about
Domain Nothing Everything else
Use Cases Domain + interfaces Adapters, Frameworks
Adapters Use Cases + external SDKs Frameworks
Frameworks (Program.cs) Everything

Test: Can you delete the database and the Domain layer still compiles? Yes → correct.

2. Event Store API Is the Integration Layer

All services communicate through the Event Store API. No direct service-to-service calls for writes.

graph LR
    B[Book-E] --> ES[Event Store API]
    R[Review-E] --> ES
    ES --> A[Accounting API]
    ES --> C[Cost API]

Why: - No coupling between services - Services can restart without affecting others - Natural audit trail (the event log IS the history) - Add new services by subscribing to events — no changes to existing services

3. Every Write Action Is an Event

Nothing happens without an event. No direct database writes, no direct API calls.

PROPOSED → APPROVED → EXECUTED

Why: - Full audit trail (bokføringsloven requires 5 years) - Review before execution - Replay capability - Undo via compensating events

4. Domain Has No I/O

Domain classes are pure functions. Data in, result out. No database, no HTTP, no file system.

// ✅ Correct: pure logic
var score = confidenceScorer.Score(proposal, pattern);

// ❌ Wrong: Domain fetches its own data
var score = confidenceScorer.ScoreFromDatabase(merchantName);

Why: - Unit testable without mocks - Portable — works in any context - No hidden dependencies

5. Secret Isolation

No service has secrets it doesn't need.

Secret Book-E Event Store API Accounting API Cost API
Discord token Yes
LLM key Yes
Postgres creds Yes
Folio token Yes
Fiken token Yes
Anthropic usage key Yes
Google Cloud key Yes
Cloudflare token Yes

6. Everything Is GitOps

No manual kubectl commands. No SSH to apply changes.

Code change → PR → Review → Merge → CI builds image →
CI updates manifest with SHA → ArgoCD syncs → New pod running

7. Agents Propose, They Don't Execute

AI agents are not trusted to execute financial operations directly.

Book-E → PROPOSE (with confidence score)
  → High confidence (known pattern): auto-approve
  → Low confidence (new merchant): Review-E or human approves
  → On approval: Event Store API dispatches to Accounting/Cost API

8. One External System Per Adapter

Each external system gets exactly one Adapter component.

Component Owns communication with
FolioProvider Folio API v2
FikenProvider Fiken API v2
EventStore PostgreSQL events table
PatternStore PostgreSQL patterns table
AnthropicUsageProvider Anthropic Admin API
GoogleUsageProvider Google Cloud Billing API
CloudflareUsageProvider Cloudflare API

9. One Repo, One Namespace, Multiple Services

All services live in the ai-accountant repo and deploy to the ai-accountant k8s namespace.

ai-accountant/
├── src/
│   ├── EventStoreApi/     ← Event Store API
│   ├── AccountingApi/     ← Accounting API
│   ├── CostApi/           ← Cost API
│   └── Shared/            ← Shared types
├── deploy/k8s/            ← All manifests
├── agent/                 ← Book-E character config
└── docs-site/             ← Architecture docs

Why one repo: - Services share types (event records, interfaces) - Deploy together via ArgoCD pointing to one path - Simpler CI — one build workflow per service - Shared docs site

10. Documentation Lives With Code

Architecture docs are in the repo, deployed automatically, versioned with git.

  • C4 diagrams as PlantUML source (.puml)
  • Non-C4 diagrams as Mermaid (inline in markdown)
  • CI test: mkdocs build --strict fails on broken docs
  • No ASCII art — diagrams as code only