d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
11 KiB
11 KiB
API Gateway
Problem
In a microservice architecture, clients must know the addresses of multiple backend services, handle different protocols, and implement cross-cutting concerns (authentication, rate limiting, logging) independently. This leads to tight coupling between clients and services, duplicated logic, and complex client code.
Solution
Place an API Gateway as the single entry point for all client requests. The gateway:
- Routes requests to the appropriate backend service based on path, headers, or other rules.
- Aggregates responses from multiple services into a single response when needed.
- Handles cross-cutting concerns: authentication, rate limiting, CORS, logging, request transformation.
- Decouples clients from service topology — clients talk to one URL.
When to Use
- Multiple backend services need to be exposed through a unified API.
- Cross-cutting concerns (auth, rate limiting, logging) should be centralised.
- Clients should be insulated from service discovery and topology changes.
- You need protocol translation (e.g., REST to gRPC).
When to Avoid
- Single-service architectures where a gateway adds unnecessary latency and complexity.
- Internal service-to-service communication (use service mesh instead).
- When the gateway becomes a monolithic bottleneck — keep it thin.
Pseudocode
class APIGateway:
routes = {
"/users/*": "http://user-service:8001",
"/orders/*": "http://order-service:8002",
"/products/*": "http://product-service:8003",
}
function handle_request(request):
if not authenticate(request):
return 401, "Unauthorized"
if rate_limited(request):
return 429, "Too Many Requests"
backend = match_route(request.path)
if backend is null:
return 404, "Not Found"
response = forward(request, backend)
log_request(request, response)
return response
Python
"""API Gateway pattern in Python — simulated routing and middleware."""
import time
from dataclasses import dataclass, field
# ── Request / Response models ─────────────────────────────────
@dataclass
class Request:
method: str
path: str
headers: dict = field(default_factory=dict)
body: str = ""
@dataclass
class Response:
status: int
body: str
headers: dict = field(default_factory=dict)
# ── Simulated backend services ────────────────────────────────
class UserService:
def handle(self, req: Request) -> Response:
if req.path == "/users/1":
return Response(200, '{"id":1,"name":"Alice"}')
return Response(404, '{"error":"user not found"}')
class OrderService:
def handle(self, req: Request) -> Response:
if req.path == "/orders/latest":
return Response(200, '{"id":101,"item":"Widget","total":29.99}')
return Response(404, '{"error":"order not found"}')
# ── API Gateway ───────────────────────────────────────────────
class APIGateway:
def __init__(self):
self.routes: dict[str, object] = {}
self.request_log: list[dict] = []
self._rate_counts: dict[str, list[float]] = {}
def register(self, prefix: str, service: object) -> None:
self.routes[prefix] = service
def _authenticate(self, req: Request) -> bool:
token = req.headers.get("Authorization", "")
return token.startswith("Bearer ")
def _rate_limited(self, req: Request, max_per_sec: int = 5) -> bool:
client_ip = req.headers.get("X-Client-IP", "unknown")
now = time.time()
window = self._rate_counts.setdefault(client_ip, [])
# Clean old entries
window[:] = [t for t in window if now - t < 1.0]
if len(window) >= max_per_sec:
return True
window.append(now)
return False
def _match_route(self, path: str):
for prefix, service in self.routes.items():
if path.startswith(prefix):
return service
return None
def handle(self, req: Request) -> Response:
# Middleware: authentication
if not self._authenticate(req):
return Response(401, '{"error":"Unauthorized"}')
# Middleware: rate limiting
if self._rate_limited(req):
return Response(429, '{"error":"Too Many Requests"}')
# Routing
service = self._match_route(req.path)
if service is None:
return Response(404, '{"error":"Not Found"}')
# Forward to backend
response = service.handle(req)
# Logging
self.request_log.append({
"method": req.method, "path": req.path,
"status": response.status,
})
return response
# ── Tests ─────────────────────────────────────────────────────
def create_gateway() -> APIGateway:
gw = APIGateway()
gw.register("/users", UserService())
gw.register("/orders", OrderService())
return gw
def test_routes_to_user_service():
gw = create_gateway()
resp = gw.handle(Request("GET", "/users/1", {"Authorization": "Bearer tok123"}))
assert resp.status == 200 and "Alice" in resp.body
print(f"PASS: /users/1 -> {resp.status} {resp.body}")
def test_routes_to_order_service():
gw = create_gateway()
resp = gw.handle(Request("GET", "/orders/latest", {"Authorization": "Bearer tok"}))
assert resp.status == 200 and "Widget" in resp.body
print(f"PASS: /orders/latest -> {resp.status} {resp.body}")
def test_unauthorized_without_token():
gw = create_gateway()
resp = gw.handle(Request("GET", "/users/1"))
assert resp.status == 401
print(f"PASS: no token -> {resp.status} {resp.body}")
def test_unknown_route_returns_404():
gw = create_gateway()
resp = gw.handle(Request("GET", "/unknown", {"Authorization": "Bearer tok"}))
assert resp.status == 404
print(f"PASS: unknown route -> {resp.status} {resp.body}")
def test_request_logging():
gw = create_gateway()
gw.handle(Request("GET", "/users/1", {"Authorization": "Bearer tok"}))
gw.handle(Request("GET", "/orders/latest", {"Authorization": "Bearer tok"}))
assert len(gw.request_log) == 2
print(f"PASS: logged {len(gw.request_log)} requests: {gw.request_log}")
if __name__ == "__main__":
test_routes_to_user_service()
test_routes_to_order_service()
test_unauthorized_without_token()
test_unknown_route_returns_404()
test_request_logging()
print("\nAll API Gateway tests passed.")
JavaScript
// api_gateway.js — API Gateway pattern in JavaScript
// ── Simulated backend services ───────────────────────────────
class UserService {
handle(req) {
if (req.path === "/users/1")
return { status: 200, body: '{"id":1,"name":"Alice"}' };
return { status: 404, body: '{"error":"user not found"}' };
}
}
class OrderService {
handle(req) {
if (req.path === "/orders/latest")
return { status: 200, body: '{"id":101,"item":"Widget","total":29.99}' };
return { status: 404, body: '{"error":"order not found"}' };
}
}
// ── API Gateway ──────────────────────────────────────────────
class APIGateway {
constructor() {
this.routes = new Map();
this.requestLog = [];
}
register(prefix, service) {
this.routes.set(prefix, service);
}
_authenticate(req) {
const token = (req.headers || {})["Authorization"] || "";
return token.startsWith("Bearer ");
}
_matchRoute(path) {
for (const [prefix, service] of this.routes) {
if (path.startsWith(prefix)) return service;
}
return null;
}
handle(req) {
// Auth middleware
if (!this._authenticate(req)) {
return { status: 401, body: '{"error":"Unauthorized"}' };
}
// Routing
const service = this._matchRoute(req.path);
if (!service) {
return { status: 404, body: '{"error":"Not Found"}' };
}
// Forward
const response = service.handle(req);
// Log
this.requestLog.push({
method: req.method,
path: req.path,
status: response.status,
});
return response;
}
}
// ── Tests ────────────────────────────────────────────────────
function createGateway() {
const gw = new APIGateway();
gw.register("/users", new UserService());
gw.register("/orders", new OrderService());
return gw;
}
function testRoutesToUserService() {
const gw = createGateway();
const resp = gw.handle({
method: "GET",
path: "/users/1",
headers: { Authorization: "Bearer tok" },
});
console.assert(resp.status === 200 && resp.body.includes("Alice"));
console.log(`PASS: /users/1 -> ${resp.status} ${resp.body}`);
}
function testRoutesToOrderService() {
const gw = createGateway();
const resp = gw.handle({
method: "GET",
path: "/orders/latest",
headers: { Authorization: "Bearer tok" },
});
console.assert(resp.status === 200 && resp.body.includes("Widget"));
console.log(`PASS: /orders/latest -> ${resp.status} ${resp.body}`);
}
function testUnauthorized() {
const gw = createGateway();
const resp = gw.handle({ method: "GET", path: "/users/1", headers: {} });
console.assert(resp.status === 401);
console.log(`PASS: no token -> ${resp.status} ${resp.body}`);
}
function testUnknownRoute() {
const gw = createGateway();
const resp = gw.handle({
method: "GET",
path: "/unknown",
headers: { Authorization: "Bearer tok" },
});
console.assert(resp.status === 404);
console.log(`PASS: unknown -> ${resp.status} ${resp.body}`);
}
function testRequestLogging() {
const gw = createGateway();
gw.handle({
method: "GET",
path: "/users/1",
headers: { Authorization: "Bearer tok" },
});
gw.handle({
method: "GET",
path: "/orders/latest",
headers: { Authorization: "Bearer tok" },
});
console.assert(gw.requestLog.length === 2);
console.log(
`PASS: logged ${gw.requestLog.length} requests: ${JSON.stringify(gw.requestLog)}`
);
}
testRoutesToUserService();
testRoutesToOrderService();
testUnauthorized();
testUnknownRoute();
testRequestLogging();
console.log("\nAll API Gateway tests passed.");
Related Patterns
- Facade — the API Gateway is a facade over multiple backend services, presenting a unified API to clients.
- Rate Limiter — the gateway enforces per-client rate limits as a cross-cutting concern.
- Circuit Breaker — the gateway can wrap backend calls in circuit breakers to prevent cascading failures.
- Backend-for-Frontend — a BFF is a specialised API Gateway tailored for one specific client type.
- Sidecar — the gateway handles cross-cutting concerns at the network edge; sidecar handles them at the service instance level.