DEV Community

Cover image for Paying for the future on purpose
Ernesto Herrera Salinas
Ernesto Herrera Salinas

Posted on

Paying for the future on purpose

There is a category of design decision where the cheap option for the version you are shipping quietly mortgages the version after it. With a solo project and no deadline, I kept choosing to pay now. Here are the two clearest ases, and the principle that connected them.

Pre-pay one: structure over shortcuts

Composition adds rules to members. A collection member, for instance, might use a child definition for its elements, hold between two and five of them, and be null
ten percent of the time. v1.1 only needs the first of those. v1.2 wants all three stacked on one member. So how should a member hold its rules?

A: one rule per member, conflicts unless whitelisted.
   Stacking N features needs an O(N^2) compatibility matrix.

B: a member is a record of independent facets (element, size, null-probability).
   Stacking is free; only two rules of the SAME facet conflict.
Enter fullscreen mode Exit fullscreen mode

Option A is less code for v1.1 today. Option B introduces a facet record now, for a feature that only uses one facet, which feels like over-building. I took B
anyway. The same logic applied one layer up: resolution became an ordered pipeline of stages rather than a stack of hardcoded if branches, so v1.2 can insert a stage by position instead of rewriting the branch.

The reason is the brief, not taste. Feature growth is an explicit goal, and there is no deadline. The design doc itself had concluded that the cheap options are the
only thing that would force v1.2 to reopen composition. When you can see the roadmap that clearly and you are not racing a clock, paying the structural cost
once is strictly better than paying interest on it every release. (This pipeline, it turns out, becomes the host for the entire coherence feature later in the series. The pre-pay compounded in my favor faster than I expected.)

Pre-pay two: a default that fails loudly

The second case is about composition boundaries. If you compose a reusable customer definition into an order, and that customer is silent about how money is generated, should a money rule you set on the order reach inside the customer?

Two pure answers, and the choice is really about how each one fails.

hermetic: the rule seals at the boundary. The customer generates its own way.
ambient:  the rule flows in. The customer's money becomes yours.
Enter fullscreen mode Exit fullscreen mode

Ambient is convenient right up until it is not. The failure mode is that a distant ancestor silently mutates a fixture you wrote and tested, and you find out by
auditing the whole chain. Hermetic fails the other way: your rule under-reaches, you see the wrong value at your own call site, and the fix is local. Local and diagnosable beats non-local and silent. So the default is hermetic: a composed definition is a sealed contract.

But hermetic under-reaches on purpose, and sometimes you really do mean "every money value in this whole graph is in euros, no exceptions." For that I reserved
an escape valve, UseDeep, that a consumer writes explicitly and that always wins, penetrating any boundary. The fork there was whether reach should be the
consumer's call (UseDeep) or the author's (Sealed/Open on the definition). I took a hybrid: sealed by default so authors get safe contracts for free, plus a consumer UseDeep that overrides anything. Two concepts, but each is in the hands of the person who knows the intent. UseDeep is greppable and visible at the call
site, which keeps even the ambient-style power loud rather than silent. Its shape is reserved now and ships in a later point release, so the default can never paint
it out.

Takeaway

When you can see the feature roadmap and you are not under deadline, pay your structural debt down before it compounds. The brief is what tells you whether you
can afford to. And when you pick a default, choose the one whose failure mode is local and visible, not the one whose convenience hides the bug several layers up.

What's next

That is the composition thread settled. Now the threads switch. The next question is not how objects compose, it is whether the data inside them looks real at all.

Next post: fake data that is not obviously fake.

Top comments (0)