Change at any moment

The fourth journey: a bounded context that exists, with its durable spec and its code, meets a change. A behaviour has to change, or the code and the spec disagree; the source the context came from moves to a new version; a request arrives that a rule forbids; the scope recorded for a source has to grow; a rule enters the spec that the form of the code cannot hold. Each is a moment you will meet, at any point of a project’s life, and in each the spec comes before the code. Why the spec is the truth is in Why Partition; how a context is created from a source is in Create a bounded context; how the code follows, through a plan, is in Deliver through a plan.

Journey 4, change a bounded context: the spec moves first, then the code; never the spec edited to match the code because the code is what runs. Two ways in. First way, a behaviour has to change, or the code and the spec disagree: the assistant, with partition-spec, says which side is wrong and why (the spec incomplete, outdated or ambiguous, or the code a bug; when it cannot tell, it asks); it corrects the spec and shows it, and when a rule or an invariant is contradicted it stops before any code; a rule copied from a source changes only through its source; you validate the new spec; when the code is wrong instead, the spec stays and the code is fixed through a plan. Second way, the source moved to a new version: you commit the new version alone, under the same file name, overwritten; partition-shape reports what moved and what breaks, a diff of meaning from the recorded commit, each change breaking, additive or neutral, and the breaks set against the code; it checks whether the split still holds, where a unit joins a context or a new context, a move, a merge or a removal is proposed, never made; a hard stop where you validate each move by name, and no answer means no writing; then partition-shape updates the specs surgically, the derived sections regenerated, the hand-written ones kept, the new version and its commit recorded. In both cases the code follows through a plan with partition-plan, the dated reason in the Decisions of the plan and only the result in the spec, delivered as in journey 3.

In the figure, a blue step is the assistant with the skill it uses; an amber step is yours.

The situation

A bounded context is built and delivered. Then something moves: a feature needs a behaviour the spec forbids, the product owner ships a new version of the PRD, sales ask for an exception to a rule, the scope you first decomposed has to grow, or a new business rule gives a thin module a state to protect. You want the change to land in the spec before it lands in the code, the rules people confirmed to change only through people, and the boundaries the architect drew to move only by the architect’s decision.

The five moments below come from the guide’s steps 05, 07, 09, 10 and 12. Each part of this article takes them in that order.

What you bring

When a behaviour has to change, or the code and the spec disagree. The feature or the behaviour, from the person who owns it: in the guide, [PO] asks that the accountant cancel an invoice and issue its correction, which the database refuses because a rule copied from the PRD says an invoice is issued once per order. When the rule in the way was copied from a source, only the owner of that source can say which requirement holds: in the guide, [PO] amends the PRD, and you commit the new version alone, under the same file name. A person then rewrites the copied rule, with its trigger and its expected effect: in the guide, [BA]. A contract the feature implements is committed alone before the prompt that reads it, as every contract (in the guide, [architect] brings a new version with a cancel operation). The design questions the plan raises are answered by the role that owns each: in the guide, [architect] decides how the rule is held in the database.

When a source moves to a new version. The new version, as a file, committed alone under the stable name of the old one, which it overwrites: in the guide, [PO] brings PRD 1.1, with a new requirement and a rule added to an old one; a contract version brought by [architect] is committed the same way. If the change moves a boundary, the architect’s choice of the way (a workshop or a proposal), then the decision and its reason: in the guide, [architect] retains a new bounded context against the option the proposal recommended. The code path of a new bounded context, from [TL]. The confirmation of every rule the new version states, from a person: in the guide, [BA]. And the answers to what the source leaves open: in the guide, [PO] says how billing learns that a customer is under fraud review, when the assistant asks instead of inventing a table or an API.

When a request contradicts a rule. The request, as it reaches the team: in the guide, [PO] passes on a request from sales that VIP customers may pay while under fraud review, which an area rule forbids. Then the answer of the person who owns the rule.

When the scope extends. The part of the source to add to the recorded scope: in the guide, the discount operations of the catalog contract, left out at the first decomposition. As for any new bounded context: the architect’s way and decision, the code path from [TL], the business rules from [BA], and the form of the hexagon with its reason from [architect].

When a context changes form. The new business rule, from a person: in the guide, [BA] gives a product can be retired; a retired product is never reactivated, to a module that was thin because its only rule checked an input. The feature that needs it, from [PO].

Your commits. Between turns you commit; the assistant never does. In the guide, the spec is committed alone, before the code that follows it.

What you say

When a behaviour has to change, or the code and the spec disagree, from the README’s Day to day:

This behaviour has to change.

or “The code and the spec disagree.” The disagreement may also show up inside a feature: in the guide, you only ask for the feature, and the stop comes from the assistant:

Plan and build this feature: (the feature, in one sentence).

Once the owner of the source has amended it and you have committed it, the source has a new version (the next moment). Then the person who owns the rule rewrites it, in the guide’s words:

Replace BR-n with the new rule of REQ-n. I confirm it. Trigger: (what starts it). Expected effect: (what it does).

To look for disagreements without a change in hand, from the README’s Day to day: “Audit <bounded-context> against its spec.” The spec-auditor agent reads the code of one bounded context against its spec, changes nothing and reports.

When a source moves to a new version, once it is committed alone, as the guide does:

The <source> changed to <version>. Bring the specs in step with it.

or, from the README’s Day to day, “The contract moved to 1.5.0.” or “What does this change of the source break?” When the writing plan is shown, validate it, and each proposal by name:

Validated. I validate the new bounded context <name> by name.

When a request contradicts a rule, nothing special: you ask for the work, as the guide does, and the stop comes from the assistant:

Plan and build this: (the request).

When the scope extends, you may name the wider scope as if decomposing anew, as the guide does on purpose:

Find the bounded contexts of <source>, area <area>, scope <the recorded scope; the part to add>.

When the assistant asks, answer:

Extend the recorded scope with <the part to add>.

When a context changes form, you give the rule first, then the feature, in two prompts, as the guide does:

Add this business rule to <bounded-context>: (the rule).

Plan and build (the behaviour that needs it).

What comes back

The spec moves first: partition-spec

When a behaviour must change, or the code and the spec disagree, the always-on instruction sends the assistant to partition-spec before any code, even in a fresh session that was never told the rule. It:

  1. Says which side is wrong, and why. A spec can be incomplete, outdated or ambiguous; the code can be a bug. When it cannot tell, it asks. It never edits the spec to match the code because the code is what runs.
  2. Corrects the spec, shows the change and gets it validated, when the spec is the wrong side. When the code is wrong instead, the spec stays and the code is fixed through a plan.
  3. Only then realigns the code, through the plan of the feature, delivered as in journey 3.
  4. Puts the dated reason in ## Decisions of the plan. The spec keeps the result, not the story.

A rule copied from a source changes only through its source. When the source contradicts itself, the assistant says so, asks the owner of the source which requirement holds, and proposes no wording for the copied rule. In the guide, the rule “An invoice is issued once per order” was copied word for word from REQ-1, and REQ-2 of the same PRD asks for a correction that issues a second invoice for the same order. The assistant says the spec is not wrong against its source: the source contradicts itself. It does not drop the unique constraint because the feature needs it, and it writes no spec and no code. A plan may open: it then holds the question as a blocker owned by the product owner, and Resume at waits on it.

Once the source is amended and its new version delivered (next moment), a person rewrites the rule. The assistant writes it on that person’s instruction, shows it and gets it validated:

# Rule Source
BR-1 An order has at most one invoice that is not cancelled. from source: REQ-1

An open question that the change answers leaves Open questions in the change that writes its answer, that answer being your word. Then the plan opens, or resumes, with the reason of the new rule in its ## Decisions; the answers the plan’s questions bring are settled in the spec first, and the code follows. A design choice the rule needs is asked, not chosen: in the guide, the assistant asks the architect how the rule is held in the database.

A new version of a source: partition-shape, the delta

You commit the new version alone, under the same name. partition-shape finds the decomposition record of the area with the commit it was derived from, sees the source changed since, and runs the delta. It writes documentation only: never code, and never a change to the source.

A merge, a removal or a move you validate is not carried out here: the assistant tells you to run partition-reshape with the validated proposal, the fifth journey, and never chains into it. A source recorded from a URL cannot be compared: the assistant asks you what changed, or to save the new version as a file.

Then the code follows, through a plan. In the guide, the plan of the refund asks how billing learns that a customer is under review, rather than inventing a table or an API.

A request that contradicts a rule: stopped before code

The always-on instruction and partition-spec hold it: when the work contradicts a business rule of the spec, a rule of the area file or an invariant of the register, the assistant stops and flags it before writing code. A statement lives at the lowest level that holds it all, and a lower level never contradicts a higher one. In the guide, a fresh session that was never told about fraud review:

In the guide, the product owner drops the request and the rule stands. Nothing changes in the project but the plan, closed with that decision in its ## Decisions, or no plan at all.

When the owner reverses the rule instead, it changes as any behaviour does, the spec first. A rule copied from a source changes through its source: its owner amends the source, and the delta of partition-shape carries the change. A rule a person wrote changes through partition-spec, the owner validating the change. Only then does the code follow, through a plan.

The scope extends: asked, never widened by itself

The scope recorded for a source never widens by itself: not to follow shared data, not for a neighbouring unit. When you ask for a decomposition with a scope that differs from the one recorded, partition-shape stops and asks: extend the recorded scope through the delta, or choose another target folder. It never overwrites, nor merges, a decomposition validated on another scope.

You choose the extension. The delta runs with it; when the source itself has not changed since the recorded commit, only the extension is handled. The units it brings are weighed against the criteria like any new unit; a new bounded context goes through the architect’s way and decision, as above. At the hard stop, the extension and each new context are validated by name. Once written, the recorded scope reads the wider value, the coverage counts the new units, and a gap the extension resolves is marked resolved, with its date, never erased. A new context whose source states no rule keeps its “TO FILL” banner until a person writes them; an area rule already there is proposed to bind it, as for a new version.

From there it is a new bounded context like any other: the rules written by a person, the form of its hexagon chosen with its reason (in the guide, thin, which still keeps its ports: a use case never calls the database directly), then a plan.

A context changes form: thin to rich, through the plan

First the spec. partition-spec writes the rule a person gives, with its source (the person and the date), shows it and gets it validated. No code changes in this step; you commit the spec alone.

Then the feature. partition-hexagon reads the new rule against its table: a status that moves under conditions, with a transition that is forbidden, calls for a rich form, a domain model. The change of form is:

  1. A decision, recorded with its reason in ## Decisions of the plan, and in docs/partition.yml: the hexagon becomes form: rich, its reason rewritten, a domain layer added to its layer map with what may depend on it.
  2. Carried out by the first tasks of the plan of the behaviour that needs it, not by a plan of its own. Those tasks move the code the domain takes into a domain layer that depends on nothing outside it, and prove the move before anything is built on it: the expected result written first, no behaviour added or changed, the real output of the verification command, and the check of the layer map finding no dependency of the domain layer on anything outside it.

It is a change of form, not a promotion. The assistant does not add a “small check” in a use case of a thin module instead: a domain layer is not grown by accretion. In the guide, a build check written from the layer map needs no new rule: the generic rule checks the new layer.

What you decide

A context that keeps growing through these changes is measured and split once, in the fifth journey.

Go deeper

Back to the series.