Modernisation path

Improve the change boundary, then the files

You do not need a greenfield rewrite. Measure one product rule on the current hop graph, collapse the hops that only exist for ceremony, then stop introducing new distributed fan-out. The largest win around an already-local API is keeping the use case end-to-end as the product grows.

Four stages

A pipeline you can run on an existing solution

01

Specify behaviour

Write operations, outcomes, and schema once. The architecture is not allowed to invent a second set of product rules. Seed data belongs here so tests and agents stop inventing identifiers.

Output: product contract
02

Collapse hops

Delete façades that only remap. Inline mediator commands that have one handler. Keep modules if they are real aggregations; drop interfaces with one implementation.

Output: shallow graph
03

Map and ritualise

Add a feature map, agent notes, and a template for new operations. Close the outcome set. Move tests onto a real host with behaviour names.

Output: cold-start rails
04

Hold the boundary

New rules stay in the application that already owns the tables. No noun microservice for a field that co-changes. Real providers and identity are the first justified splits.

Output: end-to-end ownership

Practical moves on a Layered solution

Stop the fan-out

  • One request type, not API contract plus application DTO plus command.
  • Business outcomes as a closed status, not a new exception type per rule.
  • HTTP tests appended to one spec file instead of a new mock class per handler.

Pick a posture per team

  • Modular if agents already know the modules and you want the 60% token budget.
  • Operation-local if cold agents must ship without a tour — 45% files touched, estimated ~90% first-pass completeness.
  • Do not mix both styles for the same verb. Consistency beats a clever hybrid.

What not to do while “modernising”

A noun microservice

A field that co-changes with an existing aggregate is not a process. Keep it in the owner.

BFF that orchestrates writes

Auth and header mapping only. If the method is map JSON → RPC → map JSON, delete it.

Shared kernel package

A contracts NuGet hides consumers. Copy a small DTO or generate from one schema next to the owner.

Gateway aggregation

YAML that composes two capabilities is a façade the compiler cannot check.

Event-driven core path

Agents handle “if this, then that” in one function. They handle “eventually, unless the consumer lagged” poorly.

Remap errors at every hop

Preserve the closed set. A gateway that turns a specific outcome into a generic 400 destroys agent-visible meaning.

Measure the next change

Files touched, tokens read, outcomes missed

Index the current posture at 100%. After a collapse, the same rule should move toward 60% tokens (Modular) or 45% files (Operation-local), with first-pass completeness rising as the map and tests take over search. See how the current figures were produced.