What are some common anti-patterns that appear when Hexagonal Architecture is applied across a larger codebase or multiple teams, beyond simple leaky ports, and how do they undermine the pattern's intent?
answer
- port explosion: one interface per method
- god port: one giant interface, many responsibilities
- anemic ports: mirror CRUD not business ops
- fake hexagonal: 1:1 wiring forever, no real flexibility used
- group ports by aggregate/capability, not by method or subsystem
basics
~20 sAt scale, teams often make too many tiny interfaces (a mess to navigate), write fakes that don't match real behavior, or build one giant port instead of several focused ones — each quietly brings back the same coupling and confusion the pattern was meant to prevent.
solid answer
~50 sA few recurring anti-patterns show up past small-scale adoption. 'Port explosion' — one interface per method or per call site instead of per cohesive capability — turns navigation into a maze worse than the framework code it replaced. 'God port' is the opposite failure — one enormous port covering unrelated capabilities, like a single interface for every table in the system — that reintroduces coupling and makes swapping or testing any one piece require faking the whole thing. 'Anemic ports' mirror generic CRUD operations (findAll, save, deleteById) rather than actual business operations, so business invariants that should live in the port's shape, like atomic multi-write operations, leak elsewhere. And 'fake hexagonal', where every port has exactly one implementation ever, forever, means teams pay full indirection cost for zero realized flexibility — often because the pattern was adopted as a blanket policy rather than where it earns its keep.
go deeper
Not generally expected to have encountered these at-scale failure modes yet; awareness that 'too many tiny interfaces' can be its own problem is a good sign.
Should recognize port explosion and god-port as opposite failure modes if described, even if not naming them unprompted.
Should be able to spot anemic/CRUD-mirroring ports in a real design and propose a business-operation-shaped alternative.
Should set org-level conventions and review mechanisms that catch shape-level anti-patterns automated import rules miss, and manage the trade-off of mandating vs selectively adopting the pattern across teams.
## How the failure modes shift at scale Once Hexagonal Architecture moves from a single well-scoped module to a multi-team, multi-service codebase, its failure modes shift from "did we draw the boundary at all" to "did we draw the boundary at the right granularity and for the right reason," and several recurring anti-patterns show up predictably at that scale. ## Port explosion Port explosion is the most common: a team, in the name of following the pattern strictly, defines a separate single-method interface for every distinct call the core makes outward — a `SaveOrderPort`, a `FindOrderByIdPort`, a `FindOrdersByCustomerPort`, a `DeleteOrderPort` — rather than one cohesive `OrderRepository` port grouping the related operations. The result is dozens or hundreds of tiny interface files for what should be a handful of coherent capabilities, and navigating the codebase now requires jumping through more indirection than the plain framework-coupled code it replaced, inverting the pattern's intended readability benefit. This tends to emerge when teams treat "one port per capability" as "one port per method" without a shared sense of what counts as a cohesive capability — a judgment call that benefits from explicit team convention, such as grouping ports around aggregate boundaries rather than around individual queries. ## The god port The inverse failure is the "god port": one sprawling interface covering many unrelated responsibilities, such as a single data-access port with fifty methods spanning orders, customers, and inventory, or a notification port that handles email, SMS, and push notifications all in one contract. This reintroduces the exact coupling ports are meant to avoid: - any test needing even one method from the god port must supply a fake implementing all fifty; - any change to one unrelated method risks breaking every consumer's mock setup; - swapping just the email implementation for a different vendor is impossible without touching everything else bundled into the same interface. It tends to emerge from teams retrofitting hexagonal boundaries onto an existing monolithic service class by mechanically wrapping it in one interface, rather than actually decomposing responsibilities along the lines the ports are meant to express. ## Anemic ports Anemic ports are a subtler and arguably more damaging anti-pattern at scale: a port whose method names and shapes mirror generic CRUD operations — `findById`, `save`, `deleteById`, `findAll` — rather than the business operations the core actually needs. On the surface this looks compliant, since it is technically an interface, technically implemented by an adapter, but it means the core, not the port, ends up doing the work of assembling multi-step consistency guarantees out of primitive CRUD calls, because the port never captured the actual business operation as a first-class contract. At scale, this compounds: dozens of core services all reach for generic save/find primitives on their repositories, nobody's port encodes any actual business invariant, and the supposed ports-and-adapters codebase behaves, in practice, exactly like a plain layered CRUD system wearing hexagonal clothing. ## Fake hexagonal "Fake hexagonal" is the organizational version of the problem: leadership mandates hexagonal architecture as a blanket standard across all services, so every team writes ports and adapters, but in the overwhelming majority of modules there is, was, and will only ever be exactly one implementation per port — no test double is used, tests hit a real test database directly instead — no second adapter is ever added, and the interfaces exist purely because a checklist requires them. This is expensive in aggregate: across dozens of services and hundreds of engineers, the navigation and maintenance tax of unused indirection compounds into a real productivity cost, without a single instance of the flexibility payoff the pattern is meant to buy — precisely the scenario a selective-adoption strategy warns against, just realized at organizational scale rather than in one module. ## The underlying lesson The underlying lesson across all four anti-patterns is that Hexagonal Architecture's value comes from the **SHAPE** of the boundary matching real business capabilities and real variability, not from the mechanical presence of interfaces. A principal-level response to these failure modes at scale isn't a rule like "always use ports" or "never use ports" but a small number of concrete, checkable conventions: 1. group ports around aggregate/capability boundaries, not individual methods or entire subsystems; 2. require ports to express business operations, not CRUD primitives, wherever an invariant spans more than one write; 3. require an explicit, revisited justification, real multiple adapters or a real testing/isolation need, before a new module adopts full ports-and-adapters discipline, rather than applying it by default. Some organizations enforce part of this with automated architecture-boundary checks confirming core packages have zero adapter/framework imports, combined with periodic review of port shapes themselves, since the import-boundary rule alone catches leakage but not port-explosion, god-port, or anemic-port problems, which are shape/design issues no automated rule fully catches.
- How would you distinguish, in a code review, a well-grouped port from a case of port explosion?Check whether the methods on the port operate on the same aggregate or cohesive capability and are typically needed together by callers — a repository with save/find/delete for one aggregate is cohesive; a separate single-method interface for each of those same three operations is explosion. A rough heuristic is asking whether any real adapter would ever implement only some but not all of the split-out interfaces — if not, they should probably be one port.
- What automated check catches core-to-adapter import leakage, and what does it NOT catch?An architecture-boundary rule can fail the build if core-package code imports anything from an adapter or framework package, catching leaked persistence/web dependencies reliably. It does not catch design-shape problems like port explosion, god ports, or anemic CRUD-mirroring ports, since those are still syntactically valid interfaces with no forbidden imports — that requires human architecture review.
- Why is 'fake hexagonal' harder to detect than a simple leaky port?It compiles cleanly, passes all import-boundary checks, and looks structurally correct — the only signal is behavioral/historical: every port has exactly one implementation that's never changed and no test double is ever used in its place. That pattern only becomes visible by looking across the codebase's history and adapter count, not from reading any single file.
It's like a company that mandates a formal requisition form for every single office supply down to individual paperclips (port explosion), versus a company with one all-purpose 'get me stuff' form covering laptops, snacks, and legal contracts alike (god port) — both defeat the purpose of a form designed to route the RIGHT request to the RIGHT specialized handler.
saying these in an interview costs you the question
- Thinks any interface at the boundary automatically counts as a well-designed port regardless of grouping
- Bundles unrelated responsibilities into one giant port 'to reduce the number of interfaces'
- Designs ports around generic CRUD method names by default rather than business operations
- Believes an import-boundary rule alone is sufficient to guarantee healthy hexagonal design
- Mandates hexagonal architecture org-wide with no mechanism to notice modules where it's never realizing any benefit