Kata 04: The Interface-Based Middleware Chain
Target Idioms: Interface Design, Middleware Pattern, Composition over Inheritance Difficulty: 🔴 Advanced
🧠 The "Why"
Object-oriented developers often reach for class hierarchies and inheritance when building pipelines. In Go, interfaces enable composition over inheritance. The unidiomatic approach is to create a BaseHandler class with virtual methods. The idiomatic Go way uses small interfaces composed together. This pattern powers http.Handler, io.Reader, and many standard library patterns - but developers from other ecosystems struggle to see when to split interfaces.
🎯 The Scenario
You're building a real-time analytics pipeline for user events. Each event must pass through multiple processing stages: validation, enrichment, filtering, and finally storage. New stages will be added frequently. The pipeline must be:
- Modular (add/remove stages without rewriting core logic)
- Observable (track metrics at each stage)
- Recoverable (continue processing after non-critical errors)
🛠 The Challenge
Create a middleware chain for processing user events.
1. Functional Requirements
- Process events through a configurable chain of middleware
- Each middleware can modify, filter, or reject events
- Provide metrics (counters, latencies) for each stage
- Support graceful shutdown with context cancellation
2. The "Idiomatic" Constraints (Pass/Fail Criteria)
To pass this kata, you must strictly adhere to these rules:
- Small Interfaces: Define a
Processorinterface with a single method:Process(context.Context, Event) ([]Event, error) - Middleware Composition: Each middleware must implement the
Processorinterface and wrap anotherProcessor - Functional Options: Configure middleware using functional options (e.g.,
WithMetricsCollector()) - Context Propagation: All middleware must respect context cancellation
- Zero Global State: No package-level variables for configuration or state
- Testable by Design: Each middleware must be unit-testable in isolation
🧪 Self-Correction (Test Yourself)
Test your implementation against these scenarios:
- The "Infinite Loop":
- Create a middleware that generates 2 events from 1 input
- Chain it with a filtering middleware
- Fail Condition: If events multiply uncontrollably or memory usage grows exponentially
- The "Context Leak":
- Add a middleware with a 10s timeout
- Cancel the context after 1s
- Fail Condition: If any middleware continues processing after context cancellation
- The "Interface Pollution":
- Try to add a new middleware that needs access to database connections
- Fail Condition: If you had to modify the core
Processorinterface to add database methods