Skip to content

Chain of Responsibility — Practice Tasks

Build up a middleware/handler chain. Use go test -race.


Task 1 (Junior): Approval chain (object form)

Implement an expense-approval chain: TeamLead approves ≤ $100, Manager ≤ $1,000, Director ≤ $10,000, otherwise "rejected". Each handler approves or forwards. Wire them with SetNext and test all four outcomes.

Acceptance: $50 → team lead, $500 → manager, $5,000 → director, $50,000 → rejected.


Task 2 (Junior): Middleware chain (function form)

Reimplement a request pipeline with type Middleware func(HandlerFunc) HandlerFunc. Write Logging (prints method+path then forwards) and Auth (rejects with 401 if no token, else forwards). Provide Chain(h, mw...) and assert Logging runs before Auth.

Acceptance: ordered output; missing token short-circuits with 401 and the final handler does not run.


Task 3 (Middle): Catch the dropped request

Introduce a buggy middleware that forgets to call next. Write a test that fails because the final handler never runs. Then fix the middleware.

Acceptance: test detects the drop; after the fix the final handler runs.


Task 4 (Middle): Order-dependent security test

Build Chain(routes, Logging, Auth) where Logging dumps the request body. Write a test showing that with this order, unauthenticated bodies get logged. Reorder to Auth, Logging and assert unauthenticated requests are rejected before logging. Explain in a comment why order is a security concern.

Acceptance: test demonstrates both orders; comment explains the risk.


Task 5 (Middle): Context propagation

Add a RequestID middleware that puts an ID in the context, and a final handler that reads it. Add a Timeout middleware using context.WithTimeout. Write a test proving the ID reaches the handler and that a slow handler is cancelled by the timeout.

Acceptance: handler sees the request ID; slow handler returns a context-deadline error.


Task 6 (Senior): Build once, not per request

Write a handler that (wrongly) calls Chain(...) inside ServeHTTP. Benchmark it, then refactor to build the chain once at startup. Compare allocations/op.

Acceptance: benchmark shows allocations drop to ~0 per request after the fix.


Task 7 (Senior): gRPC-style interceptor chain

Define type Interceptor func(ctx context.Context, req any, next Handler) (any, error). Implement Recovery (recovers panics into an error) and Auth. Compose them with a ChainInterceptors helper. Test that a panic in the inner handler becomes an error and that auth short-circuits.

Acceptance: panic → error (no crash); unauthorized → early return; both run in the right order.


Task 8 (Senior): Observable short-circuits

Extend Task 2's Auth to emit a structured log line naming the link and reason on every rejection. Write a test asserting that a rejected request produces exactly one such log entry and that the final handler is not called.

Acceptance: one structured rejection log per denied request; handler not invoked.


Stretch

  • Make the chain data-driven: assemble different middleware lists per route from a config map.
  • Add per-link latency metrics and verify each link records one timing sample per request.