Rung 7 β The Production URL Shortener ServiceΒΆ
Target month: Shipped by end of M11 = end of May 2027 (started in M10) Calendar deadline: May 31, 2027 Phase alignment: Phase 07 β Enterprise Spring & Data Signal level: Full-stack Spring β proves you can ship a service, not just a class
What It IsΒΆ
A fully-functional URL shortener, built with Spring Boot 3.x + Java 21, deployed to a real, publicly-reachable URL, with observability, tests, CI/CD, and documentation that meet a production bar.
The URL shortener is chosen deliberately: it is the classic system-design study problem, so shipping a real one is directly study-relevant. It has just enough moving pieces (persistence, caching, rate limiting, redirects, analytics) to demand a real architecture without ballooning in scope.
Feature scope (freeze this list; do not expand)ΒΆ
POST /api/v1/shortenβ accepts a long URL, returns a short code (7 chars, base62)GET /{code}β 302-redirect to the long URLGET /api/v1/stats/{code}β returns hit count, first-hit, last-hit timestampsDELETE /api/v1/links/{code}β auth-required, removes a linkAuth: minimal API-key auth on write endpoints (
POST,DELETE). API key stored hashed.Rate limiting: IP-based on
POST, using the Rung 5 rate limiter. This is the payoff of Rung 5.Persistence: PostgreSQL via Spring Data JPA (managed schema via Flyway)
Caching: Redis for hot short-code lookups (write-through cache on redirect)
Observability: Micrometer metrics,
/actuator/health,/actuator/metricsbehind auth, structured JSON logsDocs: OpenAPI 3 spec auto-generated via springdoc, hosted at
/swagger-ui
DeploymentΒΆ
Deploy to a real host that the public internet can hit. Options in order of preference:
Fly.io β free tier still workable for a small JVM app; global anycast is nice for a URL shortener
Railway β slightly more expensive but simpler; good Postgres+Redis integration
Render β free web service tier with limitations; acceptable
AWS EC2 t3.small + RDS + ElastiCache β expensive, but the strongest resume signal because itβs what enterprises use
Zoho Catalyst / AppSail β dogfooding-friendly if youβre a Zoho employee; strong signal in-house
The deployed URL goes in the repo README and on your portfolio landing page as a live βtry it nowβ link.
Where To PublishΒΆ
Repo:
github.com/RaghulR2003/shortlnk(or a name you like) β public, pinnedLive URL: e.g.,
shortlnk.raghulr2003.devorshortlnk.fly.dev, linked from the repo README hero sectionAPI docs:
<live-url>/swagger-ui.htmlβ accessible without auth for the docs themselvesBlog: Hashnode. Title: βBuilding a production URL shortener in Spring Boot 3 + Java 21 β what I skipped, what I couldnβt skip, and why.β Focus on decisions and tradeoffs, not tutorials.
LinkedIn: Long-form. Include the live URL. This is the first rung where a recruiter can literally play with your work.
Portfolio landing: βTry it nowβ tile with a live short-URL form embedded
Acceptance CriteriaΒΆ
FunctionalΒΆ
All five API endpoints implemented and returning correct status codes
Short codes are 7-char base62, collision-checked before insert
API-key auth working on
POSTandDELETE, correctly returning 401/403Rate limiting on
POST(e.g., 60 requests / minute / IP) via the Rung 5 libraryRedirects work in a browser with correct 302 and
LocationheaderHit count and timestamp analytics increment correctly and survive Redis restarts (Postgres is source of truth)
QualityΒΆ
Unit test coverage above 70% (measured with JaCoCo, badge in README)
Integration tests using Testcontainers for real Postgres + Redis (no mocks for these)
Load test with k6 or [Gatling] included in
load-tests/, results in READMEOpenAPI spec auto-published, Swagger UI reachable
Structured JSON logging, log correlation via MDC + request ID
Micrometer metrics exposed and viewable
DeploymentΒΆ
Live URL reachable from the public internet
CI pipeline builds, tests, and deploys on every push to
main(GitHub Actions + host-specific deploy action)Dockerfile is multi-stage, produces an image under 200 MB using
eclipse-temurin:21-jre-alpineor equivalentHealth check endpoint used by the hostβs load balancer
Environment secrets managed via host secrets store, not committed
DocumentationΒΆ
Repo README has: architecture diagram (draw.io / excalidraw), live URL, curl examples, local-dev quickstart, deployment instructions
ARCHITECTURE.mdexplains the design decisions and their tradeoffs (this is the doc a senior engineer will actually read)Blog post published with the decisions section as the core (why Postgres and not DynamoDB, why Redis and not Caffeine, why API key and not JWT)
Signal It SendsΒΆ
You can ship a service. Not a class. Not a library. A service that a stranger can hit from a browser. This is the difference the M13 pitch hinges on.
You know Spring Boot 3. Not βSpring MVC from a 2018 tutorial.β Modern Spring, with modern Java. This is directly the stack most Indian MNC Java job postings ask for.
You use real infra. Postgres, Redis, Testcontainers, Docker, a real host. Every one of these is a resume-parseable keyword.
You wire the rungs together. The rate limiter is Rung 5βs output. This rung consumes your own libraries. Nothing signals seniority faster than a portfolio where the rungs feed each other.
You care about observability. Micrometer, structured logs, health endpoints. This is what separates a hobby project from a service thatβs ready to be operated.
You write about tradeoffs. The blog post is not a tutorial. It is a decisions post. Decisions posts are what senior engineers actually read.
Common Failure ModesΒΆ
Scope explosion. βIβll add QR codes and custom aliases and OAuth and expiring URLs.β Every one of these turns a 4-week rung into a 12-week rung. Freeze the feature list on day 1.
Local-only demos. βIt works on my machine.β If it isnβt deployed to a public URL, this rung has not been completed. The live URL is the whole point.
Skipping the analytics. Just returning a short URL isnβt enough; the analytics endpoint is what makes it a service rather than a toy.
Mocked persistence. Using H2 or an in-memory Map instead of real Postgres. This kills the resume-signal. Use Postgres from day 1, with Testcontainers for tests.
No architecture doc. Just having the code isnβt enough.
ARCHITECTURE.mdis what senior engineers read before looking at the code.Free-tier expiry. Fly.io and Render will scale-to-zero or expire free tier services. Set a calendar reminder for M13 to verify the live URL is still up during job studies.
Not using the Rung 5 rate limiter. Just calling
bucket4jor another library defeats the whole point of the rungs feeding each other. Use your own.
Time EstimateΒΆ
Domain model + REST endpoints: ~10 hours
Spring Data JPA + Postgres + Flyway migrations: ~8 hours
Redis caching layer: ~5 hours
API-key auth + rate limiting integration: ~6 hours
Analytics endpoints + hit tracking: ~5 hours
OpenAPI + Swagger UI setup: ~2 hours
Micrometer + structured logging + actuator: ~4 hours
Unit + integration tests (Testcontainers): ~12 hours
Load test with k6: ~4 hours
Docker multi-stage image + deploy scripts: ~5 hours
CI/CD pipeline: ~4 hours
Deployment + DNS + TLS + host-specific config: ~5 hours
README, ARCHITECTURE.md, diagrams: ~5 hours
Blog post: ~10 hours
Total: ~85 hours over 8 weeks (~10-11 hours/week)
This is your all-of-M10-and-M11 rung. If you have not touched Spring by end of M10, you are behind.
PrerequisitesΒΆ
Rung 5 shipped (the rate limiter this depends on)
All files in
07_enterprise_spring_data/read and worked throughDocker installed and working; youβve built and run at least one image
One prior Spring Boot βtutorialβ project completed as scaffolding practice (this can be done inside
07_enterprise_spring_data/projects/)Basic Postgres + Redis knowledge (schema design, indexing, TTL, eviction policies)
Account and credit card on your chosen host (Fly.io, Railway, etc.) even if you stay in free tier β verify before M10 to avoid surprises
Stretch Goals (Optional)ΒΆ
Custom domain with TLS.
s.raghulr2003.devor similar. This adds a real DNS + TLS story to your resume. Cloudflare + Letβs Encrypt is the free path.Grafana + Prometheus dashboard. Scrape Micrometer metrics into Prometheus, view in a hosted Grafana. Screenshot in the README. Direct signal to any SRE-adjacent recruiter.
Chaos test. Kill the Redis container mid-load-test, show that the service continues to serve (from Postgres) but with degraded latency. This is the exact scenario an study partner will ask about.
Multi-region deployment on Fly.io. Fly makes this near-trivial. Adds a distributed-systems story that would otherwise wait for Rung 8.
Return to README.md Β· Next: 08_rung8_ml_capstone.md