
Next.js vs htmx + Rust. When to Use Each Stack
Compare Next.js with Axum, Askama, and htmx through browser state, contracts, caching, deployment, security, mobile clients, and project handover.
Strategy, Adapter, and Factory solve different kinds of change. Learn how to recognize the moment each pattern earns its place and when a direct function is better.

A design pattern is not proof of seniority. It is a name for a solution that has earned its keep against one particular kind of problem. No problem, no value. What arrives instead is one more file and one more interface, plus vocabulary the next developer must learn before they can change the price of one courier.
When I design business software I never open the catalogue first. The first question is what will change. The second is who owns that change, because the person who orders the new payment method is almost never the person who writes it. Strategy might answer that. So might Adapter, or Factory. Quite often the answer is a plain function with two branches in it.
Decide from the expected change, not the pattern name
The smallest abstraction that isolates a likely change is usually the right one.
One stable rule
Keep a direct function.
Changing algorithm
Consider Strategy.
Foreign interface
Isolate it with an Adapter.
Several creation paths
A Factory can hide assembly.
Take delivery pricing. The first release supports pickup and one courier, so two branches inside one function beat the class hierarchy that would replace them. The input, the rule and whatever the customer finally pays stay visible together, in one place.
The pressure arrives when the rules start moving independently. One carrier charges by weight. Another by region. Contracted customers bring their own terms. From that week on, one function carries several unrelated reasons to change, and an adjustment made for one of the carriers can quietly break the price for all the others.
Good design does not react to line count. It reacts to the number of independent reasons a file has to change. Fifty stable lines beat ten that three teams rewrite every week for three business reasons that none of them has ever discussed with the others.
Strategy fits when the task holds still while the algorithm moves. Quoting delivery is one task before the new carrier arrives and the same task afterwards, because what varies is the calculation rather than the question being asked.
type DeliveryQuote = (order: Order) => Money;
const quoteDelivery = (order: Order, quote: DeliveryQuote): Money =>
quote(order);
A Strategy does not need to be a class. In TypeScript a type and a handful of pure functions do the job. The point is that the order knows nothing about a courier's price table. The price table, in turn, never learns where the order came from. Adding a rule means adding a function, and the algorithms that have been pricing correctly in production for months stay shut, which limits the reach of the next change.
The fit is poor when one calculation is stable. It is equally poor when every variant shares the same complicated mutable state, because tiny strategy files then shuffle branches between files without isolating anything at all.
Find the real boundary first.
An ERP or a payment gateway arrives with its own names and its own data types. The carrier API adds failure modes nobody on your team invented. Let all of it into the application core and the domain starts speaking the vendor's language, so the next API revision reaches orders first, then invoicing, then screens that were never supposed to know the vendor exists.
An Adapter translates that interface into a small contract our own system owns. The domain asks for reserveStock while one ERP talks about stock movements and another exposes item reservations, and the difference stops at the integration boundary.
Testing gets easier as a side effect. A domain test needs no network and no vendor sandbox, because it checks behaviour against the contract we wrote ourselves. One integration test proves that the adapter translates the request and the response correctly, keeping any required network access within integration tests.
An Adapter is not a drawer for arbitrary mess. If it renames every field one for one against a provider interface that has been stable for years, it earns nothing. Its value grows with the semantic distance between the external model and our own, which is to say it grows exactly when the vendor names things differently from your sales team.
Factory handles creation when assembly depends on the input type. A PDF invoice needs OCR first, then invoice validation, then an accounting mapper, while the contract goes through its own extractor with another set of checks.
The caller has no business knowing the construction order of every dependency. It asks for a processor for one document type and gets one back through a shared contract.
Business judgement must not vanish inside a Factory with dozens of branches. Choosing components for a document type belongs to creation. Whether an invoice may post automatically is a domain rule, and it stays visible with a test of its own, because that rule is the answer you give on the day somebody asks why the system posted an invoice by itself.
Same feature, different place of change
Patterns earn their place when they reduce the reach of the next real change.
Before
One service knows pricing, providers and how each document is formatted.
After separation
The domain calls a small interface and change details stay behind the boundary.
Review asks the same three questions every time. Can we name the change we expect? Will the pattern hold that change behind one boundary, or will the same conditional spread across three files under three new names? And is the result clearer to someone who was not in the room when the decision was taken?
If the answer is no, the abstraction arrived early, and early abstractions are the ones nobody dares remove three years later because no one remembers what they were protecting. Code needs no protection from an imaginable future. It needs protection from what the product roadmap and the signed contracts already make certain.
Patterns do combine. An Adapter hands the domain one interface. A Strategy holds the rule that varies. A Factory assembles only what that rule needs. That only works when each part guards a different boundary. Three pattern names describing one conditional is not architecture. It is paperwork.
A business owner does not need to read a class diagram. They need to know whether adding a payment method means touching the whole application or one file with one test. A CTO needs to see where the rule lives, what contract holds it, and which test fails on the day somebody edits it without knowing why it was there.
When we design custom software, we tie every technical boundary to one change the business can actually name. The next article runs the same argument at system scale and asks whether you want a modular monolith or microservices. For what AI is doing to development work itself, read our account of human judgement in AI-assisted development.
The best pattern disappears into the codebase. The next change has an obvious home and the test has an obvious reason to exist. Nothing else in the system needs to know.
Maroš Bednár prepared the article with AI support for research and language editing. He reviewed and approved the technical conclusions, examples, and final text.

Compare Next.js with Axum, Askama, and htmx through browser state, contracts, caching, deployment, security, mobile clients, and project handover.

A buyer checklist for security, integrations, data roles, ownership, support, and exit planning before signing a software contract.
A practical guide to defining requirements, comparing suppliers, validating delivery evidence, and reaching a defensible technology-partner decision.