advanced 3 min answer

A 220000-line payments service has no ports - use cases call the ORM session the provider's HTTP SDK and the system clock directly. The suite takes 23 minutes because nearly every test needs Postgres and a stubbed provider. A principal engineer proposes a ports-and-adapters retrofit of the whole service. Roughly what does the full retrofit cost what would the first three ports buy and what would you fund?

hexagonalports and adapterstest runtimerefactoring costestimation
Show the full answer Hide the answer

The assumptions, stated

You cannot estimate this from the line count. Estimate it from call sites, which are countable before anyone commits to the work:

  • ORM session used directly inside application code: assume about 1900 sites (a grep for the session handle, minus the ones already inside repository classes).
  • Provider SDK calls: about 40 sites.
  • Direct clock reads (now(), today()): about 260 sites.
  • Tests that touch Postgres: about 70% of the suite; tests that stub HTTP: about 25%.
  • Mechanical change with review, in a codebase with a working suite: roughly 25 to 40 call sites per engineer-day, including the review and the rebase.

The arithmetic

2200 call sites at 30 a day is roughly 70 to 90 engineer-days of mechanical change, or four to five engineer-months once you add the design of the ports themselves (expect 20 to 30 of them in a service this size), a conformance suite per port so the in-memory fake cannot promise what the real adapter does not deliver, and the inevitable discovery that some use cases need queries no sane port would expose.

Call it a quarter of a team-quarter to a full team-quarter, and note what you do not get until the very end: the suite stays at 23 minutes for the whole retrofit, because the runtime only falls when the fakes exist and the tests actually use them.

What the first three ports buy

Order them by tests unblocked per unit of work, not by architectural tidiness.

  1. The clock. 260 sites, one day of codemod, and it removes every sleep and every test that fails at month end. It also buys a capability the service does not have: time travel in tests.
  2. The provider. 40 sites, one port, one recorded-interaction fake plus a conformance suite run against the real sandbox nightly. Removes the stub HTTP server from a quarter of the suite.
  3. The repository for the two or three aggregates the tests actually touch. Not all 1900 sites - the 40% that sit in the hot use cases. This is the one that moves the runtime, because it is what lets the bulk of tests run with no database at all.

Three ports, perhaps three to four engineer-weeks, and a plausible suite of 4 to 6 minutes for the tests that matter on every push, with the remaining database-backed tests moved to a stage that runs on merge. That is most of the benefit for roughly a fifth of the cost.

Which assumption dominates the error

The share of ORM call sites that sit inside use cases rather than inside code that is already an adapter. If half of the 1900 are in query objects and report builders - code that is honestly infrastructure - the retrofit shrinks by months, and those sites should keep calling SQL directly. If instead the ORM is threaded through domain services, the number grows, and so does the risk that the port you extract is a leaky one shaped like the ORM rather than like the domain's need.

The second-largest error is the fake: a fake that drifts from the adapter converts a fast suite into a confident one that is wrong, which is worse than slow.

When a full retrofit is the wrong answer

  • The service is being decommissioned or replaced inside a year. Buy the clock port, stop.
  • Every port has exactly one implementation and always will, and the suite is already fast. Then ports-and-adapters is being bought for a testability benefit you already have, and all that is left is indirection and a larger diff for every change.
  • The team cannot articulate the first question a port should answer. Cockburn's 2005 write-up and the 2024 book that follows it state the test plainly - the application should run with no user interface and no database. If a proposed port does not move the service towards that, it is ceremony.

The decision rule: fund the ports that remove a named external system from a named set of tests. Decline the ports whose justification is symmetry.