← Back to works

CASE STUDY

Taking a legacy monolith apart, one domain at a time

Orders, users and payments split into Spring Boot services behind a versioned API, with a Redis cache-aside layer on the hot catalogue reads.

Role

Senior Full Stack Developer — migration and caching

Project

Dental supply e-commerce platform

Java 21Spring BootAngularPostgreSQLRedisAWSDocker

Domains extracted

Orders · Users · Payments

Catalogue reads

Cache-aside, TTL invalidated

p95 latency

Reduced on hot endpoints

Frontend

Figma → reusable components

CONTEXT

An established e-commerce platform running as a single deployable, with a large catalogue and pricing data that is read constantly and changes comparatively rarely. The frontend was being rebuilt in Angular from Figma designs at the same time.

THE PROBLEM

Every change shared one release and one blast radius, so unrelated work queued behind whatever else was in flight. The catalogue endpoints carried the most traffic and went to the database for data that had not changed since the last request.

HOW IT WORKS

Architecture

Split along business domains rather than technical layers, so orders can ship without waiting on payments. Catalogue and pricing reads are served cache-aside from Redis.

LEGACY MONOLITHAngular clientFigma componentsAPI gatewayversioned · paginatedOrderscart · checkout · fulfilmentUsersaccounts · JWT auth · RBACPaymentscapture · refundsRediscache-aside · TTLPostgreSQLcatalogue · pricingSPLIT BY DOMAIN — NOT BY LAYERCATALOGUE + PRICING READS SERVED FROM CACHEDOMAINS EXTRACTED3 · incrementallyHOT ENDPOINTSlower p95

TRADE-OFFS

Decisions

What was chosen, what it was chosen over, and why.

Split by business domain, not by technical layer

ConsideredSeparate API, business-logic and data-access tiers

WhyA layer split leaves every feature crossing every service, so nothing gains independent deployment — the coupling moves rather than reduces. Domain boundaries let orders ship without touching payments.

Incremental extraction rather than a rewrite

ConsideredBuilding the replacement alongside and cutting over once

WhyA big-bang cutover puts all the risk on one date and stops delivery until it lands. Moving one domain at a time keeps the platform releasable throughout and limits the damage of getting a boundary wrong.

Cache-aside in Redis with TTL invalidation

ConsideredWrite-through caching, or a read replica

WhyCatalogue and pricing reads dominate the load and tolerate brief staleness, so a TTL is sufficient and far simpler than keeping a write-through cache correct. A replica would still have served every request from disk.

Versioned REST APIs with pagination on catalogue endpoints

ConsideredUnversioned endpoints returning full collections

WhyVersioning lets the Angular rebuild and the services move at different speeds without breaking each other, and paginating large catalogue responses keeps payloads bounded as the catalogue grows.

IN HINDSIGHT

What I'd do differently

  • Write the contract tests between extracted services earlier — the first integration breakages surfaced in a shared environment rather than in CI.
  • Instrument each endpoint's p95 before starting the migration, so the improvement is measured rather than inferred.
  • Decide the cache TTLs per data type up front instead of settling on them after observing staleness complaints.
Footer

Connect with me on social media.

linkedingithub

Designed and Developed with ❤️ by Shubham

Send me an email!
Let's chat on WhatsApp!