DDD + Hexagonal in Practice — F# in the Field
You have finished the by-example tracks. You know what an aggregate is. You know what a port is. Now the question is: how do they wire together in a real production codebase that ships? This tutorial answers that question using apps/ose-app-be — the F# / Giraffe / Npgsql backend of the OSE Platform — as the running domain.
Every guide in this series traces a single wiring seam: how a Giraffe handler parses an HTTP request into a command, how a domain aggregate processes that command, how an output port carries the result to a Npgsql adapter, and how an integration test swaps that adapter for an in-memory stub. No toy examples. No order-taking stories. Real files, real modules, real production decisions.
Prerequisites
Both of the following tutorials are required reading before this one:
- Domain-Driven Design — F# by Example — teaches DDD tactical patterns (aggregates, value objects, domain events, workflows) in F# using the Wlaschin order-taking domain.
- Hexagonal Architecture — F# by Example — teaches ports-and-adapters structure (primary adapters, output ports, adapter swapping, integration test seams) in F#.
This tutorial does NOT re-teach DDD or hexagonal fundamentals. Terms like aggregate, port, adapter, bounded context, and repository pattern are used without definition. If any of those feel unfamiliar, complete the prerequisite tracks first. The guides here are about wiring — how the pieces connect in production — not about what the pieces are.
Running Domain
The running domain for all guides is apps/ose-app-be — the F# / Giraffe / Npgsql backend of the OSE Application platform.
The codebase organizes around four bounded contexts:
| Bounded context | Responsibility |
|---|---|
regulatory-source | Ingests and stores regulator-published rule documents |
internal-policy | Ingests and stores company-internal policy documents |
gap-analysis | Compares the regulatory corpus against the policy corpus |
ai-orchestration | Wraps AI provider calls behind a port, keeping the domain free of vendor lock-in |
Current source lives under apps/ose-app-be/src/OseAppBe/. The codebase is mid-migration from a flat layout (Domain/, Handlers/, Infrastructure/, Contracts/) to the intended per-context layout (contexts/<ctx>/{domain,application,infrastructure}/). The contexts/ subdirectories currently contain only .gitkeep files — the intended layout scaffolding exists, the feature files come in per-context feature plans.
Both layouts appear in this tutorial. Mirror-mode guides cite populated files. Intended-layout guides describe the target structure and mark snippets explicitly.
Dogfooding Modes
Every code block in this tutorial is grounded in one of two modes:
Mirror mode (preferred) — the snippet copies a real file at authoring time. A Source: line immediately following the block links to the original file. Example:
Source: apps/ose-app-be/src/OseAppBe/Handlers/HealthHandler.fs
Intended-layout mode — when the per-context scaffolding exists but the feature file does not yet, the snippet shows the target file. A callout marks this explicitly:
New file — intended layout. Scaffolding exists at
apps/ose-app-be/src/OseAppBe/contexts/<context>/<layer>/.
Bare snippets without a source citation or an intended-layout callout are forbidden by the checker for this tutorial.
Guide Numbering
Guides are numbered monotonically across all difficulty tiers (1, 2, 3 … N). Guide 1 appears in the beginner tier, Guide N appears in the production tier. This makes cross-references unambiguous: "see Guide 5" means the same guide regardless of which tier page you are reading.
Learning Path
- Beginner (Guides 1–6) — One context = one hexagon, reading the current flat layout, domain types without framework imports, application service signatures, output port as F# function type alias, Giraffe handler as primary adapter, and the composition root in
Program.fs. - Intermediate — Npgsql adapter behind the repository port, domain event publisher port, integration test seam wiring, and contract codegen consumed by a handler.
- Advanced — Cross-context Anti-Corruption Layer, docker-compose integration harness, AI orchestration port + adapter swap, and domain event flow inside a context.
- Production — Deployment hooks, observability port, failure-mode wiring, and migration notes.
Last updated May 15, 2026