← Work

Client contract · 2022 – 2026

Rebuilding a Platform,v1 to v2

What seven services taught me that the first architecture couldn't.

ROLE  Architect and lead engineer — both generationsSTATUS  v1 superseded — v2 architecturally completeFOR  a regional information-security consultancy
  • NestJS
  • TypeScript
  • NATS
  • Kubernetes
  • Skaffold
  • Helmfile
  • MongoDB
  • Redis
  • React / Next.js
  • Angular
v1: 7 services, split per entityv2: 7 services, split by bounded context+ 6 managed infra components

The same product, architected twice. The second version isn't bigger; it's decomposed along different lines. This page is about that difference.

For hiring managers

Two generations of the same system are better evidence than one polished diagram: they show the decomposition was reasoned about.

v1 split services roughly per entity — auth, users, entities, projects, mail, dashboard — deployed with Skaffold against raw kubectl manifests, with a React/Next.js front end.

v2 splits by bounded context instead — the user domain absorbs auth, users, organizations, permissions and subscriptions into one service that owns identity end to end; the core domain owns the entire compliance workflow; the document, analytics, ops and support domains each own a capability rather than a table. Infrastructure moved from hand-written manifests to Helmfile-managed operators (MongoDB Community Operator, Redis Operator, MinIO Operator). The front end moved to Angular.

Where v1's shape came from: in May 2022 I completed Stephen Grider's Microservices with Node JS and React, which teaches exactly this stack: Node services, NATS, Skaffold, Kubernetes, decomposed per entity. v1 is that pattern applied to a real product, months later. v2 came from living with it.

I learned the pattern from a course, applied it to a real product, and found where it stops working.

The lesson: v1's split followed the data model, which meant a single user-facing action fanned out across three or four services and every feature touched multiple repos. v2's split follows the domain, so most changes land in one service. Service boundaries drawn around entities produce distributed monoliths; boundaries drawn around business capabilities produce independently deployable systems.

I only reached that conclusion by building the wrong version first.

For clients

The rebuild cut the number of services a typical feature has to touch, replaced hand-maintained Kubernetes manifests with managed operators that handle backup and failover, and moved secrets out of version control. The result is a platform a team can extend without needing the original architect in the room.

The hard problem

Knowing when to stop cutting

The instinct in microservices is that smaller is better, and v1 followed it — a service per entity looks clean on a diagram. It fails because entities aren't independent: creating a user touches auth, users, and mail simultaneously, so three services deploy together, share a release cadence, and are microservices in name only.

v2 asks a different question of every boundary: can this service be deployed, scaled, and reasoned about alone? The user domain is larger than any single v1 service, deliberately: identity is one bounded context, and splitting it produced coupling without buying independence. The question isn't how small services can be; it's where the seams hold under change.

Trade-offs

What it cost

DecisionBoughtPaid
Rebuild rather than refactor v1Clean boundaries; no legacy compromise carried forwardMonths of work reproducing behaviour that already existed
Larger services in v2Most changes land in one place; genuinely independent deploysLess granular scaling; the user domain is now big enough to need internal structure of its own
Helmfile + operators over raw manifestsBackup, failover and upgrades handled by the operatorA new layer of abstraction to learn and debug when it misbehaves
React (v1) → Angular (v2)Consistency with the rest of the client workReplaced a working front end purely for consistency

The v1→v2 rebuild was the right architectural decision and an expensive one. A team under delivery pressure would likely have been better served by refactoring boundaries incrementally inside v1 — same destination without a rewrite, shipping throughout. The rewrite was affordable here because the platform wasn't yet serving users. That context makes the decision defensible.