Skip to content

kamilch1k/cacheguard

Repository files navigation

cacheguard

Small .NET backend service for resilient API traffic.

cacheguard is a portfolio project around production-style API behavior: rate limiting, request coalescing, circuit breaking, response caching, and stale fallback when an upstream service fails.

It is intentionally not a CRUD app. The project shows how a backend service can protect an origin API, reduce duplicate work, and keep returning useful data when dependencies are temporarily unhealthy.

What it demonstrates

  • per-client token-bucket rate limiting
  • response caching with separate fresh and stale windows
  • circuit breaker with closed, open, and half-open states
  • request coalescing for concurrent identical GET requests
  • stale-if-error behavior when an origin returns 5xx
  • a built-in demo origin so the project is testable without external services
  • unit tests for the core resilience policies
  • GitHub Actions and Docker packaging

Quick start

dotnet test
dotnet run --project src/CacheGuard.Api --urls http://localhost:18081

In another terminal:

curl -i "http://localhost:18081/proxy/catalog/42?value=first"
curl -i "http://localhost:18081/proxy/catalog/42?value=first"

The first request should return x-cache: MISS; the second should return x-cache: HIT.

Prove stale fallback

Warm the cache:

curl -i "http://localhost:18081/proxy/catalog/42?value=stable"

Wait longer than the default fresh cache TTL of 20 seconds, then simulate an origin failure for the same cache key:

curl -i "http://localhost:18081/proxy/catalog/42?value=stable&_cg_status=503"

The response should include:

x-cache: STALE
x-cacheguard-warning: origin-failed-served-stale

_cg_status and _cg_delayMs are demo-only controls. They are passed to the built-in origin but excluded from the cache key so failure modes can be tested against the same cached resource.

Prove rate limiting

The default token bucket allows 8 requests per client/path per 10 seconds:

for i in {1..10}; do curl -i "http://localhost:18081/proxy/rate-demo" ; done

Later responses should return 429 with a Retry-After header.

Prove circuit breaking

Trigger repeated origin failures:

curl -i "http://localhost:18081/proxy/unhealthy?_cg_status=503"
curl -i "http://localhost:18081/proxy/unhealthy?_cg_status=503"
curl -i "http://localhost:18081/proxy/unhealthy?_cg_status=503"
curl -i "http://localhost:18081/proxy/unhealthy?_cg_status=503"

After the threshold is reached, the service returns 503 with {"error":"circuit_open"} until the open window expires.

Snapshot endpoint

curl -s http://localhost:18081/admin/snapshot

The snapshot exposes counters for origin calls, cache hits, stale hits, coalesced requests, rate-limited requests, circuit-open requests, and demo origin requests.

Configuration

Variable Default Meaning
UPSTREAM_BASE_URL built-in demo origin Real upstream API base URL
CACHEGUARD_RATE_LIMIT_CAPACITY 8 Requests allowed per client/path window
CACHEGUARD_RATE_LIMIT_WINDOW_SECONDS 10 Token bucket refill window
CACHEGUARD_CACHE_TTL_SECONDS 20 Fresh cache duration
CACHEGUARD_STALE_TTL_SECONDS 120 Stale fallback duration after freshness expires
CACHEGUARD_CIRCUIT_FAILURE_THRESHOLD 3 Consecutive origin failures before opening
CACHEGUARD_CIRCUIT_OPEN_SECONDS 15 Open circuit duration before half-open probe

Tech

.NET 10, ASP.NET Core Minimal APIs, HttpClientFactory, xUnit, GitHub Actions, Docker.

About

Resilient .NET API gateway demo: rate limiting, request coalescing, circuit breaker, stale cache fallback, tests, Docker, CI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors