A distributed URL shortener built with .NET microservices, showcasing real-world system design principles — including API Gateway with YARP, JWT authentication, Redis-based counter and response caching, Custom Rate Liming, RabbitMQ-driven analytics, and PostgreSQL persistence (with EF Core and Dapper).
- Gateway-First Architecture: Centralized entry point using YARP for routing, JWT validation, TLS termination, and request transformation.
- Precision Rate Limiting: Custom sliding-window and token-bucket algorithms for per-user throttling.
- Event-Driven Analytics: Click events published via RabbitMQ, aggregated asynchronously in Redis, and flushed atomically to PostgreSQL.
- Cache-Optimized Redirection: Redis-backed caching layer enabling ultra-fast short-link resolution with fallback to PostgreSQL.
- DDD-Inspired Services: Independently deployable microservices modeled around clear bounded contexts — Shortening, Redirection, and Analytics.
- Authentication & Authorization
- Verifies and validates JWT tokens issued by the Auth service.
- Applies fine-grained authorization policies per route and downstream service.
- Reverse Proxy & Routing
- Acts as the system’s entry point and reverse proxy, routing requests to appropriate downstream microservices via YARP.
- Rate Limiting
- Protects downstream services from overload using custom rate-limiting middlewares, including:
- Sliding-Window Log (precise per-user request tracking)
- Token Bucket (smoother refill-based throttling)
- Protects downstream services from overload using custom rate-limiting middlewares, including:
- Request / Response Transformation
- Injects contextual metadata (e.g.,
UserIdfrom JWT claims) into outgoing requests. - Normalizes or aggregates responses from multiple services when required.
- Injects contextual metadata (e.g.,
- Response Aggregation
- For composite endpoints, aggregates partial results from multiple services into unified API responses.
- TLS Termination
- Terminates HTTPS traffic, handling encryption/decryption before routing to internal services.
- YARP (Yet Another Reverse Proxy)
- PostgreSQL (for configuration & rate-limit persistence via EF Core)
- ASP.NET Core 9.0
- URL Shortening
- Accepts long URLs and generates unique short codes using configurable strategies:
- Counter-Based Strategy: Base-62 encoding of incrementing counters from Redis (synced to Postgres) (fast, deterministic).
- Hash-Based Strategy: Base-62 encoding of truncated MD5 hashes (compact but requires collision handling).
- Accepts long URLs and generates unique short codes using configurable strategies:
- Persistence & Expiration
- Persists mappings of
LongUrl ↔ ShortCodealong with metadata (user, creation date, expiry). - Supports TTL-based cleanup for expired links.
- Persists mappings of
- Redis (for counters and caching)
- PostgreSQL with EF Core
- ASP.NET Core 9.0
- Short URL Resolution
- Resolves short codes to their corresponding original URLs via Redis cache or database lookup.
- Response Caching
- Implements cache-aside pattern in Redis to minimize repetitive DB reads.
- Event Publishing
- Publishes asynchronous click-tracking events (e.g.,
ShortUrlClicked) to RabbitMQ, consumed later by the Analytics Service.
- Publishes asynchronous click-tracking events (e.g.,
- Redis (response cache)
- PostgreSQL (via Dapper for lightweight querying)
- RabbitMQ (for event messaging)
- ASP.NET Core 9.0
- Event Consumption & Aggregation
- Consumes click events from RabbitMQ and aggregates metrics (click counts, timestamps) in Redis.
- Periodic Flush
- Periodically flushes batched analytics to PostgreSQL using atomic upserts (
ON CONFLICT DO UPDATE).
- Periodically flushes batched analytics to PostgreSQL using atomic upserts (
- API Exposure
- Provides REST endpoints for analytics queries (e.g., total clicks, top links per user).
- Redis (temporary counters and batch buffers)
- PostgreSQL with EF Core
- RabbitMQ (message queue for analytics ingestion)
- ASP.NET Core 9.0
- Kafka-based event propagation between services
- UI dashboard for click analytics
- Sliding window rate limiting using Redis with atomic operation via Lua scripts
- Read replica of URL database via CDC with Debezium for scaling reads and fault tolerance
- Cassandra for analytics data persistence
- Shard PostgreSQL for scaling writes
- Load balance reads with YARP for scaling reads and fault tolerance
- Write-Through and Write-Behind caching in Redis via Redis Gears.
All routes are proxied through the Gateway Service (http://localhost:5403), which handles authentication, routing, and rate limiting.
| # | Method | Endpoint | Description | Auth |
|---|---|---|---|---|
| 1 | POST | /user/register |
Registers a new user with email and password | ❌ Public |
| 2 | POST | /user/login |
Logs in a user and returns a JWT token | ❌ Public |
| 3 | GET | / |
Health check for the Gateway | ❌ Public |
| 4 | POST | /shorten |
Shortens a long URL and stores it with owner metadata | ✅ Bearer JWT |
| 5 | GET | /user/urls |
Returns all shortened URLs created by the authenticated user | ✅ Bearer JWT |
| 6 | GET | /echo.link/{shortCode} |
Redirects the short URL to its original destination | ❌ Public |
| 7 | GET | /analytics |
Returns aggregated analytics (click counts, timestamps, etc.) | ✅ Bearer JWT |
- Endpoints marked ✅ require a valid Bearer JWT in the
Authorizationheader:Authorization: Bearer <your_jwt_token>
- Docker & Docker Compose installed
git clone https://github.com/yourusername/EchoLink.git
cd EchoLinkdocker compose up -dThis spins up:
gateway-serviceshortening-serviceredirection-serviceanalytics-serviceauth-servicepostgres-urlspostgres-userspostgres-analyticsredisrabbitmq
docker pshttp://localhost:5403
