Work  /  UpGreg  ·  upgreg.ai ↗

Case study

Four modules, one gateway, no rewrites.

How a business platform was carved up so that no one piece of it could ever hold the others hostage again — and why that carve-up saved three months on the next project.

6 wks from scoping to the first release
4 self-contained modules in service
0 rewrites since going into production
2 modules reused as they stood on another product

The problem

A product that had to grow, with no way of knowing where

The initial need was clear: user accounts, notifications, a back office. What was less clear was what came next. A young product changes its mind — about its model, about its users, sometimes about its very business.

The classic trap at this stage is the monolith: everything moves fast for three months, then every change touches everything, every deployment becomes risky, and the slightest failure stops the lot. The opposite trap — a constellation of microservices — costs a small team more than it returns.

So the question asked at scoping was not “which technology?” but “what is the smallest boundary that lets one piece be replaced without reopening the others?”

What we did

Four decisions, written down before the first line of code, and honoured ever since.

Self-contained modules behind a single gateway

Each domain — accounts, notifications, administration — is a service of its own, with its port, its health endpoint and its manifest. No module calls another directly: everything goes through the gateway or an event bus. The practical consequence: a module can be replaced without touching the rest, and a failed module does not take the product down with it.

A single database, with numbered migrations

One database per module would have been doctrinally clean and operationally ruinous at this size. One database, then — but with a disciplined schema and numbered, idempotent migrations, each referenced in the deployment script. A migration that isn't listed there is never run: that is the most common production failure in this trade, and it is prevented by an automatic check before every deployment.

A reproducible deployment, from one authorised machine only

Containers, automatic routing and certificates, environment variables derived from a version-controlled template. The deployment is a script, not a sequence of gestures: it runs from a single machine, logs what it does, and knows how to roll back.

Observability from the first increment

Health status per module, structured logs, error reporting, usage metrics: put in place in the first increment, not after the first outage. That is what makes it possible to say what happened, rather than guess at it.

Outcome

What it changed, in concrete terms

  • First useful version live six weeks after scoping, then delivery in increments.
  • No rewrites since going into production: the boundaries held.
  • The accounts and notifications modules were taken over as they stood on another product.
  • A failure in one module has never brought the whole platform down.
  • Operations are provided by the same firm as the development: no passing the buck.

Straight talk

What we'd do differently

  • Health routes declared too late. Placed after the dynamic routes, they were being captured by them. An hour lost, and since then a written rule: static routes first, always.
  • A cache introduced too early. It was masking a genuine query problem. Removed, the query fixed, the cache put back for the right reasons.
  • The first back office, too generic. Written to cover hypothetical needs; half of it was never used. Since then: we only write an admin screen after seeing the manual task it replaces.

Foundations to lay, or an existing system to carve up?

Scoping is a short, invoiced increment. It ends with a costed plan — and sometimes with “keep what you have, fix three things”.