Designing a store that can disappear on command
A feature flag that truly removes a storefront — not just hides it — and a cache that fails without taking the page down with it. Two small reliability patterns from a real ecommerce build.
Two reliability decisions on a recent ebook storefront project turned out to matter more in practice than most of the feature work around them: what happens when the cache goes down, and what happens when the business needs to turn the entire store off — genuinely off, not just visually hidden.
A feature flag that removes routes, not just hides UI
The common implementation of a "store closed" flag is a conditional in the layout: if the flag is off, show a banner or a maintenance page instead of the storefront. That's fine for a simple UI toggle, but it leaves every underlying route and API endpoint still live and reachable — someone who knows or guesses the URL can still hit checkout, still hit the product API, still probe whatever the storefront normally exposes.
This project's flag works differently: when the store-wide flag is off, the relevant routes don't exist to be requested at all — there's nothing behind the banner to find by poking around. That's a meaningfully different guarantee. "The button is hidden" and "the endpoint is gone" sound similar in a demo and behave very differently under a curious or adversarial visitor.
The same pattern shows up in the pre-launch access lock: before the store opens, the site is reachable only with a time-limited access code, and what an outside visitor sees reveals nothing about the catalog, pricing, or launch date. It's the same underlying principle as the store-closed flag — don't rely on the UI to keep something private if the routes behind it are still answering.
Cache as an accelerator, not a dependency
The storefront uses Redis to speed up its highest-traffic pages — the kind of caching that's an easy, standard win. The detail that mattered more than the speedup itself: what happens when Redis is unavailable. The app falls back to reading directly from the database rather than erroring out or serving a blank page. Traffic that would otherwise have hit a warm cache instead hits the database a little slower, but the site stays up.
This is a distinction worth being explicit about during design, because the default failure mode of "just add a cache" is often the opposite: a cache client that throws on connection failure, an unhandled rejection, and a page that 500s even though the actual data is sitting right there in Postgres, two feet away in system terms. Caching should make a working system faster. It shouldn't be load-bearing for the system working at all.
The common thread
Both patterns come from the same habit: asking "what does this actually guarantee, not just what does this look like it guarantees" during design, before either failure mode shows up in production. A hidden button and a removed route look identical in a walkthrough. A cache hit and a cache-with-fallback look identical when the cache is healthy. The difference only shows up exactly when it matters, which is precisely why it's worth deciding on purpose rather than discovering by accident.