Master Hexagonal Architecture in Rust

Hexagonal (or “ports and adapters”) architecture in Rust aims to keep core business logic independent of infrastructure like HTTP servers and databases, often by using traits and interfaces to separate pure logic from I/O. Commenters are sharply divided: supporters value easier testing, clearer domain models, and flexibility to swap implementations, while critics see over‑abstraction, performance costs, and harder‑to‑navigate code for changes that rarely occur in practice. Many advocate starting with simple, concrete designs and only introducing these patterns when complexity and testing needs clearly justify the extra indirection.

What “hexagonal architecture” means

  • Generally described as “functional core, imperative shell”: core domain logic isolated from I/O and infrastructure.
  • Related to domain-driven design, onion architecture, and “pure vs impure” separation: pure, testable core; thin, impure adapters for HTTP, DB, FS, clock, etc.
  • Ports and adapters are defined in terms of business use cases (e.g., UserFetcher, PurchasesFetcher), not specific technologies.

Perceived benefits

  • Makes the domain independent of UI, database, and transport, so logic is clearer and more testable.
  • Enables swapping adapters for tests (DB → in‑memory store, Postgres → SQLite, HTTP → CLI) without changing core logic.
  • Helps avoid domain code depending directly on low-level concerns like SQL connections.
  • Some see it as a natural fit with functional design, pure functions, and property testing.

Critiques and skepticism

  • Many view it as over‑engineering, Java/C#‑style “enterprise” patterns imported into Rust.
  • Complaints about excessive indirection, boilerplate, and “where is the actual behavior?” debugging pain.
  • Several argue that database or HTTP server swaps almost never happen; when they do, performance, semantics, and schema changes dominate, not call‑site refactors.
  • Concerns that abstractions leak, ossify implementations, and impede performance and simple JOIN-heavy or streaming queries.

Testing and dependency injection

  • Supporters emphasize decoupling to enable fast unit/integration tests without heavy DB setup.
  • Skeptics prefer end‑to‑end tests with real databases (e.g., containers) and see DB fakes as misleading and brittle.
  • Debate over DI frameworks vs. simple constructor/trait injection; some miss Go‑style simplicity, others want better Rust DI tooling.

Rust‑specific perspectives

  • Some say the “bad” example mirrors idiomatic Axum and is fine for many APIs.
  • Others warn that heavy abstraction fights Rust’s ownership/lifetime model and costs performance, pushing everything into heap‑allocated, loosely typed layers.
  • Overall theme: start simple, let real complexity justify architecture; hexagonal patterns are useful in some contexts but harmful when applied preemptively.