Splitting the Monolith Before It Hurts: Service Boundaries in a Growing Platform
Identifying the first service boundary to extract from our monolith — and how we drew the line without over-engineering.
Context
Our platform started as a single Next.js app with API routes and a shared PostgreSQL database. As we added more client products and features, the codebase grew. We started to feel the pain: deployments were slow, a bug in one area could take down everything, and it was hard for different parts of the team to work independently. We needed to extract something — but we didn't want to go full microservices. The question was: what's the first boundary?
Constraints
- Small team — we couldn't afford to run and debug a complex distributed system
- Shared database — we weren't ready for service-owned databases and eventual consistency
- Existing API contracts with external clients — we couldn't break them
Architecture
We identified the notification and email-sending logic as the first extraction candidate. It had clear boundaries: it consumed events (user signed up, order placed) and produced side effects (send email, push notification). It didn't need to participate in the main request-response flow. We extracted it into a separate Node.js service that subscribes to a queue (or polling a jobs table). The monolith publishes events; the notification service consumes them. We kept the shared database for now — the notification service reads user/tenant data it needs, but we documented that as technical debt. The key was picking a boundary that was low-risk and high-value: notifications failing doesn't break the core product.
Alternatives considered
- Extract the auth layer first: Auth is in the critical path of every request. Extracting it would have been high-risk and required changing every service. We wanted a lower-friction first step.
- Go straight to event-driven microservices with Kafka: We didn't have the operational maturity for Kafka. A simple queue or polling was enough for our scale and gave us room to learn.
Lessons learned
- Pick the first boundary by impact and risk. Notifications were high impact (we could move fast) and low risk (failures were isolated).
- Shared database between monolith and new service is a stepping stone, not the end state. Document the eventual split.
- API contracts between services matter. We defined the event payload schema and versioned it from day one.