A reorganization plan found dozens of domain directories and several older modules. It assigned them to a set of proposed bounded contexts, selected an apparently isolated location-and-address context as a safe starting point, and described where its files should move.
The map was detailed. It was also missing the part which determined whether the boundary was real.
In the running system, the legacy application kept service-point policy while calling separate location and postal applications. Location lookup called postal lookup. Each application owned different state. Booking, validation, labels, returns, settings, administration, and health checks all consumed some part of the flow.
The source folders described where code happened to live. They did not describe the system which would have to keep working while it moved.
A noun can organize files without owning a decision
Directory inventories are useful. They find duplicate concepts, mixed naming, old modules, and code which has escaped any recognizable home. They are a good way to begin reading a large application.
The mistake is treating the inventory as the conclusion.
Suppose these folders all contain geographic nouns:
addresses
postal codes
coordinates
pickup locations
country rulesIt is tempting to draw one Geography box around them. That may be a useful
source module. It says nothing yet about whether the same boundary owns their
decisions, data, deployments, or failures.
The retained plan made exactly this jump. It mapped current folders to target contexts, assigned each target a conventional set of layers, and proposed moving the most isolated context first. The plan was explicit about destinations. It had not established the runtime graph which made one context isolated.
A redesign needs at least five maps:
| Map | Question it answers |
|---|---|
| call map | who asks whom at runtime? |
| state map | who is authoritative for each fact? |
| effect map | what can commit, fail, or escape independently? |
| consumer map | which workflows rely on the behavior? |
| change map | which decisions have actually moved together? |
The directory tree can seed those maps. It cannot replace them.
Follow one request until it stops moving
The quickest correction is not a complete architecture diagram. Pick one important request and follow it.
In the retained case, a service-point lookup begins in a legacy application. That application owns customer-facing input rules and shipping-specific filters. It sends a location query to a separately deployed application. Some location providers need postal information, so the location application calls a third application which owns postal search and import state.
booking and validation
|
v
service-point policy in the legacy application
|
v
location catalogue and enquiry state
|
v
postal search and import stateThe arrows are not proof that the three applications are well designed. They are proof that moving a folder cannot be reviewed as a local namespace change.
For each arrow, record:
- the request and result meanings;
- synchronous or asynchronous delivery;
- timeout and failure behavior;
- fallback behavior;
- authentication and versioning;
- which side can retry safely; and
- what the caller tells its own caller when the dependency is unavailable.
This turns “the location code” into inspectable contracts. It also reveals when two calls which share a noun serve different product decisions.
State ownership changes the apparent boundary
The source plan grouped postal codes, coordinates, addresses, and service points because they looked related. The retained storage tells a more useful story.
The postal application owns imported postal records and lookup state. The location application owns a catalogue of locations plus cached enquiries. The legacy application owns customer and shipment configuration which changes how those locations are filtered, validated, and presented.
That is three kinds of authority:
postal application
"what postal data do we know?"
location application
"what pickup locations did providers supply?"
legacy application
"which locations are valid for this shipment and customer?"One database would not automatically make those decisions one responsibility. Three databases do not automatically make them three good services. The point is narrower: a target structure which never names the authoritative state cannot tell us what a move means.
Write down every state transition involved in the chosen request:
- where external data enters;
- where it is normalized;
- where it is stored;
- where derived or cached data is stored;
- what invalidates that derived state;
- which workflow applies customer-specific policy; and
- which system can repair each incomplete result.
That list often moves a proposed seam. A seemingly independent query may own a cache whose invalidation depends on imports elsewhere. A small validation class may encode the only customer-facing interpretation of data owned by another application.
Consumers show the cost of getting the seam wrong
The local service-point package had more than one caller. It participated in shipment validation, booking, label generation, return flows, customer settings, administration tools, and service-health reporting.
Those consumers do not all need the same thing:
- validation needs a reliable eligibility answer;
- booking needs a stable remote identity;
- label generation needs printable address data;
- settings need configuration choices;
- administration needs diagnosis; and
- health reporting needs dependency status.
A single broad location client can hide those differences. Splitting by caller can duplicate remote policy. The consumer inventory does not select either design by itself. It tells us which contracts a proposed design must preserve.
This is where a redesign should become concrete. For each consumer, name the smallest result it needs and the failure it can handle. Then compare those needs. Shared meaning suggests one capability. Different consistency, latency, or failure requirements may justify separate application-facing ports even when one adapter serves them.
History separates a stable seam from a tidy one
Current code shows today’s dependencies. History shows why they keep changing.
The retained histories contain related changes in all three applications: postal search and import behavior, location-provider retrieval and cache behavior, and legacy validation or presentation policy. They also contain technical migrations which moved layers without changing the product decision.
Do not count every co-change equally. For a useful change map, classify each change:
business rule
external provider contract
data migration
failure or recovery policy
performance work
mechanical move
tooling or formattingTwo files moving in the same namespace migration is weak cohesion evidence. Two applications repeatedly changing to support one customer-visible rule is stronger coordination evidence. A production fix which changes cache invalidation and lookup behavior may expose a state boundary which the class names never mentioned.
The goal is not to calculate an architecture score. It is to find the decisions which a new boundary would force to coordinate.
Ownership is evidence you have to ask for
Source history can show who edited a file. It cannot show who has authority to change a rule, accept a compatibility break, or own a failed import.
In the retained case, the applications did not have exclusive teams. They were collectively owned, with uneven familiarity. That makes a team-shaped service map especially unreliable: repository authorship would turn familiarity into authority without anyone making that decision.
Record ownership by decision and operation instead:
| Responsibility | Owner to identify |
|---|---|
| postal data quality | person or group which can change import policy |
| provider catalogue correctness | owner of retrieval and repair |
| shipment eligibility | owner of the customer-facing rule |
| cross-application contract | owners on both sides |
| incident diagnosis | operator with authority to repair state |
“Backend” or “everyone” may describe current staffing. It does not answer who can approve a contract change. If ownership is genuinely shared, keep that uncertainty visible rather than inventing a cleaner organization chart.
Directory-first design is still useful in a bounded case
Moving files can be the whole job when the behavior, state, deployment, and ownership stay unchanged. A namespace cleanup, presentation-layer move, or framework adapter extraction can improve navigation without pretending to redraw the business.
A directory-led proposal is also a useful hypothesis. “These classes look like one capability” gives the investigation somewhere to start.
It becomes a redesign plan only after the runtime and state maps agree with it, or after the proposal explicitly explains why they should change. If the goal is merely navigability, say so. Do not attach service-extraction promises to a source move.
Reject the plan before choosing the replacement
The retained evidence is sufficient to reject the claim that the location-and-address group was an isolated starting point. It is not sufficient to prove the correct final boundaries. No completed redesign outcome or measured delivery improvement survives in the evidence, so neither belongs in the conclusion.
The companion synthetic reproduction
makes the distinction
smaller. A directory inventory suggests one Addressing context. Runtime
calls reveal three deployables, state inspection reveals three owners, and
five consumers need different parts of the result. A short change ledger then
shows which changes cross those boundaries and which are mechanical.
That is the standard to apply before moving real code:
- select one important request;
- trace calls, state, effects, and consumers;
- classify the changes which touched it;
- identify decision and operational ownership;
- state what remains uncertain; and
- only then propose a seam.
Reading the directory tree is not wrong. Stopping there is. A redesign begins when the plan explains how the running system can change without losing the information its current shape was carrying.