Work

Projects focused on architecture, boundaries, and maintainability. Each is a study in making deliberate trade-offs.

Subscription Management Service

Subscriptions that don't break. A backend built to make invalid states impossible.

Subscription Management Service architecture diagram

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

Minly — URL Shortener

An early learning project with one standout: fully automated deployment on release.

Minly — URL Shortener architecture diagram

What It Is

A simple URL shortener — paste a long URL, get a short one, and the redirect just works. Built in 2022 when I was learning Go, it's intentionally minimal. But the interesting part isn't the feature set — it's the deployment pipeline. Every GitHub release automatically builds, tests, and deploys both the backend (to Heroku) and frontend (to Vercel) without any manual steps.

Why It Exists

I wanted to build something real, not just follow tutorials. The goal was to own the full cycle: design → build → deploy → maintain. By keeping scope small, I could focus on doing each part properly rather than building half of something impressive.

Key Decisions

  • No framework on backend — Go's net/http with chi for routing. Middleware and handlers are explicit, not magic
  • Interface-driven storage — UrlStore interface abstracts MongoDB. Swapping storage wouldn't touch the service layer
  • Dependency injection in main — All components receive dependencies at construction. Main acts as the composition root
  • Deploy on release, not on merge — Clear audit trail, no accidental deploys, batched changes, easy rollback targets
  • Graceful shutdown — Server listens for OS signals, finishes in-flight requests, then closes connections cleanly

Trade-offs

Prioritized

  • Full ownership — Rather than building more features, I focused on owning the complete lifecycle
  • Deployability — The CI/CD pipeline was as important as the code itself
  • Learning fundamentals — No frameworks, no magic. Just standard library and clear structure

Deprioritized

  • Analytics — Not the point of this project
  • Custom aliases — Scope creep for a learning exercise

Explicitly Not Done

  • Rate limiting — Learning project, not production traffic
  • Authentication — Unnecessary for the use case
  • Caching — Would be premature optimization here

Go · MongoDB · Next.js · GitHub Actions · Heroku · Vercel

→ View on GitHub