Saltar a contenido

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, Result for error propagation, cargo workspaces for crate-level enforcement of dependency direction.
  • Type-driven design: make illegal states unrepresentable. A User whose email might be unverified is a different type from a VerifiedUser. This is the New Type pattern operating at scale.

17.2 Mechanical Detail

  • Workspace layout for a hexagonal service:
    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 binary
    
    Each adapter crate depends on domain and nothing else from this workspace. Enforce with cargo-deny [bans] rules and cargo modules graph checks.
  • Ports as traits: prefer async_trait only when needed (allocation per call). When the trait is single-impl per binary, use a generic parameter Service<Repo: UserRepo> to avoid the Box<dyn> cost.
  • Error boundaries: domain errors are typed enums; adapter errors map to domain errors at the seam. Never let a sqlx::Error leak 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 bans rules forbidding domain from depending on any I/O crate (tokio, sqlx, reqwest, etc.). The CI must fail on a violation. This is the architectural test.

Comments