Week 17 - Hexagonal Architecture and Domain Modeling in Rust¶
17.1 Conceptual Core¶
- Hexagonal (ports-and-adapters) treats the domain as the center, with explicit ports (traits) describing the interactions the domain needs from the outside world, and adapters (concrete impls) plugging in real I/O. The compiler enforces the boundary; the trait is the contract.
- Rust is unusually good for hexagonal architecture: trait objects (or generics) at the seam,
Resultfor error propagation,cargoworkspaces for crate-level enforcement of dependency direction. - Type-driven design: make illegal states unrepresentable. A
Userwhose email might be unverified is a different type from aVerifiedUser. This is the New Type pattern operating at scale.
17.2 Mechanical Detail¶
- Workspace layout for a hexagonal service:
Each adapter crate depends on
service/ crates/ domain/ # pure types and traits, no I/O deps application/ # use cases, depend on domain adapters/ postgres/ # impl domain ports against sqlx kafka/ # impl event-bus port http/ # impl HTTP-handler port using axum bin/ api/ # wires adapters into a runnable binarydomainand nothing else from this workspace. Enforce withcargo-deny[bans]rules andcargo modulesgraph checks. - Ports as traits: prefer
async_traitonly when needed (allocation per call). When the trait is single-impl per binary, use a generic parameterService<Repo: UserRepo>to avoid theBox<dyn>cost. - Error boundaries: domain errors are typed enums; adapter errors map to domain errors at the seam. Never let a
sqlx::Errorleak to the HTTP handler-it's a layering violation and a security risk (it leaks schema details).
17.3 Lab-"A Hexagonal URL Shortener"¶
Build a workspace implementing a URL shortener:
- domain crate: ShortUrl, UrlAlias newtypes, a UrlRepository trait.
- application crate: Shorten, Resolve use cases.
- adapters/postgres crate: implements UrlRepository with sqlx.
- adapters/http crate: axum handlers using the application layer.
- bin/api crate: composition root.
- An adapters/in-memory crate used by tests, so application logic is testable without a database.
17.4 Idiomatic & Clippy Drill¶
clippy::module_name_repetitions,clippy::too_many_lines,clippy::struct_field_names. These shape API ergonomics, not correctness-but in a hexagonal codebase, ergonomics is maintainability.
17.5 Production Hardening Slice¶
- Add
cargo deny check bansrules forbiddingdomainfrom depending on any I/O crate (tokio,sqlx,reqwest, etc.). The CI must fail on a violation. This is the architectural test.