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.

Entities (Engines)      — knows nothing about the outside world
  ↑ used by
Use Cases (Managers)    — knows Entities + interfaces for I/O
  ↑ used by
Adapters (ResourceAccess) — knows Use Cases + external systems
  ↑ used by
Frameworks (Program.cs)   — wires everything together

Test: Can you delete the database and the Engines still compile? Yes → correct.

2. Services Communicate Through Events, Not Direct Calls

Between services, use the event store. Not HTTP calls.

❌ Book-E calls Review-E directly
✅ Book-E writes event → Review-E reads event

Why: - No coupling between services - Services can be offline, restarted, or replaced without breaking others - Natural audit trail - Easy to add new services that react to existing events

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) - Review before execution - Replay capability - Undo via compensating events

4. Engines Have No I/O

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

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

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

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

5. Secret Isolation

No service has secrets it doesn't need.

Service Has Does NOT have
Book-E Discord token, LLM key Folio/Fiken keys, DB credentials
Accounting API Folio/Fiken keys, DB credentials Discord token, LLM key

Why: - Compromising one service doesn't expose the other's secrets - Principle of least privilege

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

Why: - Reproducible deployments - Audit trail in git - Rollback = revert a commit - No snowflake configurations

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: system executes

Why: - AI can hallucinate amounts, accounts, VAT rates - Financial errors have real consequences - Trust is built over time (patterns → auto-approve) - Human stays in the loop for new situations

8. One External System Per Resource Access Component

Each external system gets exactly one ResourceAccess component.

FolioProvider   ← only thing that talks to Folio API
FikenProvider   ← only thing that talks to Fiken API
EventStore      ← only thing that talks to events table
PatternStore    ← only thing that talks to patterns table

Why: - Change the API? Change one file. - Swap Folio for another bank? Replace one component. - Rate limiting, retries, auth — all in one place.

9. Start Simple, Evolve

Don't build for scale you don't have. Build for the principles, not the infrastructure.

Phase 1: Single pod, in-memory, :latest tags
Phase 2: Postgres, SHA tags, ArgoCD
Phase 3: Event sourcing, review process
Phase 4: Gateway+worker split, KEDA scaling

Each phase adds capability without breaking the previous one. The Clean Architecture layers stay the same.

10. Documentation Lives With Code

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

  • Not in Confluence, Notion, or someone's head
  • Updated in the same PR as the code change
  • Deployed to Cloudflare Pages on merge
  • C4 diagrams generated from PlantUML source