When to ship vs build it right in Magento 2

Every Magento 2 project hits the sprint where "do it properly" and "ship it now" pull in opposite directions, and the engineer in the middle has to choose. This is a working engineer’s framework for that call, built to avoid Magento over-engineering without swinging to the opposite failure of shipping junk: the Five Whys to confirm you are solving the real problem, YAGNI to resist speculative scope, the Pareto principle to find the 20 percent that carries the value, and an OODA loop to keep correcting after you ship.

The lead: building it right at the wrong time still costs you

The instinct of a good engineer is to build it right: clean service contracts, a proper entity, extensible config. On a long-lived Magento 2 store that instinct is usually correct, but timing matters as much as quality. An abstraction built for a requirement that has not arrived yet is, in the codebase, indistinguishable from the wrong abstraction. You pay maintenance, cognitive load, and DI compilation cost on a seam nobody is using. That is over-engineering, and it hurts exactly as much as under-building — it just hides better in code review.

The decision is rarely "good code versus bad code". It is "the right code now, the right code later, or code you will delete". Frame it that way and the choices stop being a matter of taste. The framework below is not a single verdict, it is a loop: observe the real symptom, orient with the right principle, decide a stance, act, then measure and go round again.

The baseline: what a disciplined team already does

A competent Magento 2 team is not cowboy-coding. They have a definition of done, code review, coding standards, a CI pipeline running phpcs and phpstan, and they write modules instead of core hacks. That baseline is real and worth defending.

What the baseline usually does not include is an explicit, named decision about scope and root cause. Scope creep arrives quietly: a ticket says "add a gift message field" and three days later there is a new EAV entity, a repository, a GraphQL resolver, and an admin grid for a varchar nobody will ever query. The discipline is present; the framing to say "not yet" is missing. The tools below add that framing, and the OODA loop turns them from a one-time checklist into a habit that compounds across sprints.

The tradeoff: do it right, ship pragmatic, or defer entirely

Most build decisions on an ecommerce project collapse into three stances. Naming them out loud, in the ticket, is half the battle. A fourth move — solving the requirement with a primitive the platform already ships — depends on platform knowledge deep enough to deserve its own treatment, covered in the companion piece on rebuilding native Magento features.

Do it right now: the craftsman stance

Build the proper abstraction immediately: service contracts, a clean entity, extension points, full test coverage. Correct when the requirement is well understood, the surface is central (checkout, pricing, tax), and rework later would be expensive because other code will depend on it.

Strengths: Lowest long-term cost when the requirement is stable and central. Future contributors inherit a clean seam. Best for code that sits on the path of money moving through the store.

Costs: Highest upfront cost and the highest risk if the requirement is wrong. Abstractions chosen before the second real use case are guesses, and a wrong guess is harder to remove than missing code is to add. This is the stance that produces over-engineering when applied too early.

Ship the pragmatic version: the Pareto stance

Build the 20 percent of the feature that delivers 80 percent of the value, ship it, and let real usage decide whether the rest is worth building. The gift message becomes one order column and a checkout plugin, not an entity. Correct when the value concentrates in one slice and the remainder is speculative.

Strengths: Fastest path to value and to feedback. You learn what customers actually use before paying for the full build. Keeps the codebase small, which keeps it fast to reason about.

Costs: If the slice succeeds, you may pay a refactor to grow it properly later. The pragmatic version has to be labelled as such, or it calcifies into permanent technical debt that everyone forgot was a deliberate shortcut.

Defer it entirely: the YAGNI stance

You Aren’t Gonna Need It: do not build the abstraction, the config toggle, or the "we might want multi-store later" hook until a concrete requirement forces it. Correct for speculative flexibility, for the second and third use case nobody has requested, and for the generic plugin system someone wants "just in case".

Strengths: Zero cost for things that never ship. Removes the maintenance and cognitive tax of unused code. Keeps DI compilation and the dependency graph lean.

Costs: YAGNI is easy to weaponise as an excuse for not thinking. Deferring a genuinely known requirement is not YAGNI, it is denial. The skill is telling a speculative requirement apart from a known-but-inconvenient one.

The missing piece across all three is knowing which stance the situation actually calls for. Pick the stance from the symptom and you will guess wrong often. That is where the Five Whys earns its place.

A working example: the category page that "needs a rebuild"

A mid-size Magento 2 store, roughly 40k SKUs on Hyvä. The product owner reports that category pages feel slow on mobile, and someone in planning proposes the craftsman answer: a headless rebuild on a separate frontend. That is months of work, and it is over-engineering aimed at a symptom nobody has diagnosed. Before committing a quarter to it, run the Five Whys on the actual complaint. This is the observe and orient half of the loop: gather the real signal, then frame it correctly.

Problem: Category pages are slow on mobile.

1. Why? LCP is ~4.2s on a mid-tier Android over 4G.
2. Why? The largest element is a hero image weighing ~1.8 MB.
3. Why? Category and product images are served as full-size JPEGs, no WebP.
4. Why? No image optimisation runs in the deploy pipeline.
5. Why? It was never set up; the team assumed the CDN handled format negotiation. It does not.

Root cause: unoptimised images, not the rendering stack.

People call it the Five Whys, but the count is not sacred. Stop when you reach a cause you can act on. Here it lands at five; sometimes it is three. The discipline is asking "why" past the first plausible answer, because the first answer ("the frontend is slow") is the one that sells you the expensive rebuild.

Now the stances sort themselves. The headless rebuild was the craftsman stance aimed at the wrong root cause. The Pareto fix is image optimisation, a small slice carrying most of the win. The YAGNI call is the rebuild itself: defer it until a measured requirement, not a vibe, demands it.

Confirm the root cause before touching code:

curl -s -o /dev/null -w "ttfb=%{time_starttransfer}s\n" https://store.example/category/shoes
bin/magento dev:profiler:enable

If a custom block is also in the mix, the second-order fix is the classic N+1. Pareto again: one query change, large effect.

// Before: one repository call per product in the loop
foreach ($productIds as $id) {
    $product = $this->productRepository->getById($id);
}

// After: one collection load, limited to the fields you actually render
$collection = $this->collectionFactory->create()
    ->addAttributeToSelect(['name', 'price', 'small_image'])
    ->addIdFilter($productIds);

The pragmatic image fix can be a drop-in rather than a bespoke pipeline. See the image optimizer module for WebP and AVIF delivery with originals kept as fallback, and the image optimisation baseline article for the verification step. To see where request time actually goes before you guess, request-level tracing helps: RequestTrace.

Verification: closing the OODA loop

OODA loop: Observe the symptom, Orient with Five Whys, Decide the stance, Act and implement, then repeat

The Five Whys is a one-shot tool; it finds a root cause once. What keeps over-engineering out of a Magento 2 codebase over a quarter is treating each fix as one turn of an OODA loop — observe, orient, decide, act — and then measuring whether the decision was right so the next one is better informed. Reinforcement, not a single verdict. Three checks close the loop.

First, the metric the complaint was actually about. Re-measure LCP and TTFB on the same device class and network, not on your laptop. If LCP drops from ~4.2s to ~1.5s after the image fix, the Pareto slice carried the value and the rebuild was correctly deferred.

bin/magento cache:clean
curl -s -o /dev/null -w "ttfb=%{time_starttransfer}s total=%{time_total}s\n" https://store.example/category/shoes

Second, the test that locks the fix in place. Add a regression check so the N+1 cannot creep back, for example asserting that the query count for the block stays flat as the product count grows. A fix without a guard is a fix with a half-life.

Third, and most telling for YAGNI: track whether the deferred work was ever genuinely requested. If three months pass and nobody asks for the headless rebuild, the deferral saved real money. If a concrete, measured requirement does arrive, you now build it with usage data instead of a guess, which is the craftsman stance applied at the right time. That signal — what the store actually needed versus what planning assumed — feeds back into the next loop’s observe step. Each turn sharpens your instinct for which stance a symptom calls for, which is the whole point of running it as a loop rather than a one-off decision.

One honest caveat. Pragmatism has a failure mode of its own: a "temporary" shortcut with no label and no follow-up ticket becomes permanent debt. Whenever you take the Pareto slice, write the deferred remainder into the backlog with the condition that would trigger it ("revisit if gift messages exceed X percent of orders"). YAGNI defers the build; it does not delete the memory of the decision. The loop only reinforces good judgement if you record what you decided and why.

Related reading

  • Martin Fowler, "Yagni" — the canonical write-up on the difference between deferring and neglecting.
  • Pareto principle — the 80/20 framing and where it breaks down.
  • Five Whys — origins in the Toyota Production System and its limits as a single-cause tool.
  • OODA loop — John Boyd’s decision cycle and why speed of iteration beats one perfect plan.

Related Topics

← Previous

Written in collaboration with AI (Claude, by Anthropic). Ideas, verification, and accountability are mine; research and drafting are AI-assisted. Full disclosure → · Found an error? Tell me.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *