Subscription Management Service
Subscriptions that don't break. A backend built to make invalid states impossible.
What It Is
A complete backend system for managing recurring subscriptions — the kind that powers SaaS billing, membership platforms, and streaming services. It handles everything: sign-ups, automatic renewals, cancellations that honor your paid period, and email reminders. Behind the scenes, background workers run continuously to process thousands of subscriptions without human intervention.
Why It Exists
Ever had a subscription charge you after you cancelled? Or get stuck in a weird state where you're neither active nor cancelled? That's what happens when subscription systems are built as an afterthought. I wanted to build one where those bugs literally cannot happen — the architecture itself prevents invalid states.
Key Decisions
- State machine for lifecycle — A subscription can only be Active, Cancelled, or Expired. Invalid transitions don't compile
- Dual service interfaces — External callers pass user ID for authorization. Internal callers (scheduler) are trusted and skip it. The boundary is explicit
- Background workers inside the app — Scheduler and workers run as goroutines, share no state except through services. No external job queues needed
- Structured error types — Every error carries a semantic code that maps to an HTTP status. Clients get consistent, typed responses
- Repository interfaces in domain — Services depend on interfaces. MongoDB implementations live at the edge. Swapping databases doesn't touch business logic
Trade-offs
Prioritized
- Correctness — A renewal should never run twice. A cancelled subscription should never charge again
- Testability — Every component can be tested in isolation with fake clocks and mock databases
- Explicit boundaries — Each layer has one job. Cross-layer calls go through contracts, not hacks
Deprioritized
- Minimal code — There's deliberate ceremony. More files means clearer responsibilities
- Framework magic — No autowiring, no decorators. Dependencies are visible and intentional
Explicitly Not Done
- Distributed tracing — It's a single service. Structured logs are sufficient
- Event sourcing — The state machine already captures what happened and when
- gRPC — HTTP/JSON is simpler, debuggable with curl, and fine at this scale
Go · MongoDB · Redis · Clean Architecture · Background Workers
→ View on GitHub