Skip to content
C.W.K.
Stream
Lesson 01 of 04 · published

Domain Never Migrates In

~12 min · boundaries, framework, discipline, design

Level 0Loose Parts
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

The Rule Written in the Same Breath as the Purpose

The shared repository's charter has two sentences, and the second one points the opposite way from the first. Plumbing gets one owner. Domain code never migrates into the kit.

The second sentence exists because the first one creates a gradient. Once a shared place exists, it is the cheapest place to put anything two apps need, and the cost of each individual deposit is invisible. The rule is the counterweight, and it has to be stated at birth — retrofitting it after the shared layer has learned three domains is not a rule, it is a rewrite.

The Apps Already Forbid It of Each Other

What makes the rule sharp here is that it was already load-bearing between the apps themselves. Each sibling's architecture document forbids copying the others' domain spines — one says outright that the journey-and-day axis must not be carried over unexamined, because a journal that inherits a trip container has silently acquired a beginning and an end, which is the exact thing an open timeline is defined by not having.

A shared repository is the backdoor around that. Nobody would copy a journey container into a health app; everybody would happily consume a shared "timeline container" module that happens to be a journey container with the nouns filed off. Same import, different route, and this time it arrives with the authority of the shared layer behind it.

Shared code inherits the authority of the shared repository, so a bad abstraction spreads faster there than by copy-paste. Copying a file is visible and feels like a compromise; importing from the shared layer feels like doing it properly. That asymmetry means the shared layer needs a higher bar than any individual app, not a lower one — the exact opposite of the instinct that says "put it in the shared place so everybody benefits".

What the Rule Costs, Honestly

Some duplication survives on purpose. Every app in this family has a composer and a record card. They look alike, they solve the same user problem, and they are domain-toned all the way down — what fields exist, what the placeholder says, what an attachment means. So they stay separate, and the family accepts three of them.

The invariant they share is enforced somewhere else entirely: each app's own architecture document requires that a composer offers attachments, a prompt macro, and a way to ask the assistant. That is a shared requirement with three implementations, and it is a completely legitimate answer — one that a purely code-level view of duplication cannot even express.

Code

The gradient, one defensible step at a time·text
step 1  a module is 90% generic; the 10% is one app's rule
        -> "just add a flag"                        looks fine

step 2  a third app needs a variation of that rule
        -> "make the flag an enum"                  looks fine

step 3  a fourth app needs behavior the enum can't express
        -> "accept a callback"                      looks fine

step 4  the callback needs context the kit doesn't have
        -> "pass the app name so we can look it up" <- THE LINE

At step 4 the shared module can be WRONG ABOUT AN APP, which means
it has knowledge of apps, which is the thing the membership test
excludes. But nobody arrives at step 4 as a decision - they arrive
by doing step 3 again.


THE TWO CANARIES, both cheap enough to run in the kit's own suite:

  1. no kit function takes an app identifier as a parameter
     grep -n "app: str\|appName\|tenant" kit/**  -> must be empty

  2. no kit module BRANCHES on a consumer name (comments do not
     count - a provenance docstring naming the three engines a
     module was extracted from is a record, not knowledge)
     grep -n "== .travel.\|== .journal.\|== .health." kit/**

Both are crude, and both would have fired at step 4 while the
review at step 4 said "this is a small change, it just passes a
string".

External links

Exercise

Grep your shared libraries for the names of your applications, tenants, or environments. For every hit, decide which of three things it is: a legitimate configuration lookup, knowledge that should be injected, or a branch that means two designs are wearing one name. Fix one of the second kind today, and write down the one you decide to leave.
Hint
Configuration lookups keyed by app name are the ambiguous case. The distinguishing question is whether the shared module can be WRONG about an app — if an unknown app name raises an error, the module knows which apps exist, and that is knowledge rather than configuration.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.