Overview
Chain of Responsibility decouples request senders from receivers by giving multiple objects the opportunity to handle a request. The pattern chains receiving objects and passes the request along the chain until an object handles it. Each handler in the chain contains a reference to the next handler and decides whether to process the request or pass it forward.
The pattern emerged from the need to avoid coupling the sender of a request to its receiver, particularly when multiple objects might handle the request. Rather than the sender knowing which specific object should handle the request, the pattern allows the request to travel through a sequence of potential handlers.
Common applications include event handling systems, middleware architectures, logging frameworks, authentication pipelines, and validation chains. Web frameworks often use this pattern for request processing, where each middleware component examines the request and either handles it, modifies it, or passes it to the next component.
# Basic chain structure
class Handler
attr_accessor :next_handler
def handle(request)
if can_handle?(request)
process(request)
elsif @next_handler
@next_handler.handle(request)
end
end
end
The pattern consists of three primary components: the Handler interface defining the request handling method, Concrete Handlers that implement specific handling logic, and the Client that initiates requests into the chain. The chain forms at runtime, allowing dynamic configuration of the handling sequence.
Key Principles
The Chain of Responsibility operates on several fundamental principles that distinguish it from direct object communication patterns.
Single Responsibility Per Handler: Each handler in the chain addresses one specific concern or responsibility. A handler evaluates whether it can process the request based on its criteria, processes it if appropriate, and otherwise delegates to the next handler. This separation prevents handlers from containing complex conditional logic about all possible request types.
Dynamic Chain Construction: The chain assembles at runtime rather than compile time. Handlers connect through references to successor handlers, allowing the chain order and composition to change based on configuration or runtime conditions. This dynamic nature enables the same handlers to participate in different chains for different contexts.
Request Propagation Control: Each handler controls whether the request continues along the chain. A handler may process the request and stop propagation, process the request and continue propagation, or skip processing entirely and forward the request. This control mechanism differs from rigid pipelines where every stage must execute.
class BaseValidator
attr_accessor :next_validator
def validate(data)
return false unless valid?(data)
return true unless @next_validator
@next_validator.validate(data)
end
def valid?(data)
raise NotImplementedError
end
end
class LengthValidator < BaseValidator
def initialize(min_length)
@min_length = min_length
end
def valid?(data)
data.length >= @min_length
end
end
class FormatValidator < BaseValidator
def initialize(pattern)
@pattern = pattern
end
def valid?(data)
data.match?(@pattern)
end
end
Loose Coupling Between Handlers: Handlers remain independent of other handlers in the chain. A handler knows only about the Handler interface, not about concrete handler implementations. This independence allows handlers to be reused in different chains and tested in isolation.
Implicit Receiver: The client sending the request does not specify which handler will ultimately process it. The chain determines the appropriate handler through the propagation mechanism. This implicit receiver selection simplifies client code and centralizes handling logic within the chain.
The pattern supports both homogeneous chains where all handlers share the same interface and heterogeneous chains where handlers may implement different interfaces but share a common propagation mechanism. Most implementations favor homogeneous chains for clarity and maintainability.
Termination Conditions: Chains require clear termination conditions. A handler processes the request and stops propagation, the request reaches the end of the chain without being handled, or an error condition interrupts the chain. Proper termination prevents infinite loops and ensures predictable behavior.
class AuthenticationChain
def initialize
@handlers = []
end
def add_handler(handler)
@handlers << handler
link_handlers
self
end
def authenticate(credentials)
return false if @handlers.empty?
@handlers.first.handle(credentials)
end
private
def link_handlers
@handlers.each_cons(2) do |current, next_handler|
current.next_handler = next_handler
end
end
end
Design Considerations
Chain of Responsibility suits scenarios where multiple objects might handle a request, the appropriate handler should be determined automatically, and the set of handlers should be specified dynamically.
When to Apply Chain of Responsibility: Use this pattern when the handling logic naturally decomposes into sequential steps with different responsibilities. The pattern excels in request processing pipelines, validation sequences, event handling hierarchies, and middleware stacks. Consider the pattern when handlers may change frequently or when the handler selection logic would otherwise clutter client code.
The pattern addresses scenarios where direct coupling between sender and receiver creates inflexibility. In systems where new request types or handling strategies appear regularly, the chain provides extension points without modifying existing code.
Trade-offs and Limitations: Chain of Responsibility introduces several trade-offs. Request handling becomes less explicit since the client does not specify the handler directly. Debugging can prove challenging when determining which handler processed a request or why a request was not handled. Performance may degrade with long chains since the request potentially traverses many handlers.
The pattern may result in requests passing through the entire chain without being handled. Applications must address unhandled requests explicitly, either through a default handler at the chain's end or through error handling when no handler processes the request.
class RequestProcessor
def initialize
@chain = build_chain
end
def process(request)
result = @chain.handle(request)
raise UnhandledRequestError, "No handler processed #{request.type}" if result.nil?
result
end
private
def build_chain
# Chain construction with specific handler order
cache = CacheHandler.new
auth = AuthenticationHandler.new
rate_limit = RateLimitHandler.new
business = BusinessLogicHandler.new
cache.next_handler = auth
auth.next_handler = rate_limit
rate_limit.next_handler = business
cache
end
end
Comparison with Alternative Patterns: Chain of Responsibility shares similarities with other behavioral patterns but serves distinct purposes. The Command pattern encapsulates requests as objects but does not inherently support sequential processing. The Strategy pattern selects one algorithm from a family but does not chain operations. The Decorator pattern adds responsibilities to objects but focuses on enhancement rather than conditional handling.
Composite pattern shares structural similarities with Chain of Responsibility in that both involve recursive structures. However, Composite focuses on part-whole hierarchies while Chain of Responsibility emphasizes request delegation.
Handler Ordering Considerations: The sequence of handlers in the chain significantly impacts behavior. Handlers that filter or reject requests should typically appear early in the chain to avoid unnecessary processing. Handlers with side effects should be positioned carefully to ensure they execute only when appropriate. Performance-critical handlers may be placed early to optimize common cases.
Some chains benefit from dynamic reordering based on handler effectiveness or request patterns. Adaptive chains measure handler performance and adjust ordering to optimize throughput.
Chain Composition Strategies: Chains can be composed statically at initialization, built dynamically based on request properties, or configured through external configuration. Static composition provides predictability and performance. Dynamic composition offers flexibility for complex routing logic. Configuration-based composition separates chain definition from code, enabling runtime adjustments.
class DynamicChain
def initialize
@handler_registry = {}
end
def register_handler(name, handler)
@handler_registry[name] = handler
end
def build_chain(handler_names)
handlers = handler_names.map { |name| @handler_registry[name] }
handlers.each_cons(2) do |current, next_handler|
current.next_handler = next_handler
end
handlers.first
end
end
Ruby Implementation
Ruby's dynamic nature and object model provide several approaches to implementing Chain of Responsibility. The pattern integrates naturally with Ruby idioms including modules, procs, and method chaining.
Class-Based Implementation: The standard implementation uses classes for handlers with explicit next handler references.
class Handler
attr_accessor :next_handler
def handle(request)
if can_handle?(request)
process(request)
elsif @next_handler
@next_handler.handle(request)
else
nil # Request not handled
end
end
protected
def can_handle?(request)
raise NotImplementedError
end
def process(request)
raise NotImplementedError
end
end
class ContentTypeHandler < Handler
def can_handle?(request)
request.content_type_missing?
end
def process(request)
request.set_content_type('application/json')
@next_handler&.handle(request)
end
end
class AuthenticationHandler < Handler
def initialize(auth_service)
@auth_service = auth_service
end
def can_handle?(request)
request.requires_authentication?
end
def process(request)
unless @auth_service.valid?(request.credentials)
raise AuthenticationError, 'Invalid credentials'
end
@next_handler&.handle(request)
end
end
class LoggingHandler < Handler
def initialize(logger)
@logger = logger
end
def can_handle?(request)
true # Logging applies to all requests
end
def process(request)
@logger.info("Processing request: #{request.id}")
result = @next_handler&.handle(request)
@logger.info("Completed request: #{request.id}")
result
end
end
Proc-Based Chains: Ruby's first-class functions enable chain implementation using procs, reducing boilerplate for simple handlers.
class ProcChain
def initialize
@chain = []
end
def add(handler_proc)
@chain << handler_proc
self
end
def call(request)
execute_chain(request, 0)
end
private
def execute_chain(request, index)
return nil if index >= @chain.size
handler = @chain[index]
next_handler = ->(req) { execute_chain(req, index + 1) }
handler.call(request, next_handler)
end
end
# Usage
chain = ProcChain.new
.add(->(req, next_handler) {
return false if req[:value] < 0
next_handler.call(req)
})
.add(->(req, next_handler) {
req[:doubled] = req[:value] * 2
next_handler.call(req)
})
.add(->(req, next_handler) {
puts "Final value: #{req[:doubled]}"
true
})
chain.call(value: 5)
Module-Based Composition: Ruby modules provide an alternative approach using mixin composition and method aliasing.
module ChainLink
attr_accessor :next_handler
def call(request)
result = handle_request(request)
return result if result == :stop
@next_handler ? @next_handler.call(request) : request
end
def handle_request(request)
:continue
end
end
class ValidationLink
include ChainLink
def handle_request(request)
if request[:data].nil?
request[:errors] = ['Data required']
return :stop
end
:continue
end
end
class TransformationLink
include ChainLink
def handle_request(request)
request[:data] = request[:data].upcase
:continue
end
end
class PersistenceLink
include ChainLink
def initialize(repository)
@repository = repository
end
def handle_request(request)
@repository.save(request[:data])
request[:saved] = true
:stop
end
end
Rack-Style Middleware: Ruby's Rack specification exemplifies Chain of Responsibility in web contexts.
class Middleware
def initialize(app)
@app = app
end
def call(env)
@app.call(env)
end
end
class TimingMiddleware < Middleware
def call(env)
start_time = Time.now
status, headers, body = @app.call(env)
duration = Time.now - start_time
headers['X-Response-Time'] = duration.to_s
[status, headers, body]
end
end
class AuthMiddleware < Middleware
def initialize(app, auth_service)
super(app)
@auth_service = auth_service
end
def call(env)
token = env['HTTP_AUTHORIZATION']
unless @auth_service.valid_token?(token)
return [401, {'Content-Type' => 'text/plain'}, ['Unauthorized']]
end
@app.call(env)
end
end
class CachingMiddleware < Middleware
def initialize(app, cache)
super(app)
@cache = cache
end
def call(env)
cache_key = env['PATH_INFO']
cached = @cache.get(cache_key)
return cached if cached
response = @app.call(env)
@cache.set(cache_key, response)
response
end
end
# Building the middleware stack
app = ->(env) { [200, {'Content-Type' => 'text/plain'}, ['Hello World']] }
stack = TimingMiddleware.new(
AuthMiddleware.new(
CachingMiddleware.new(app, cache),
auth_service
)
)
Builder Pattern Integration: Combining Chain of Responsibility with Builder pattern simplifies chain construction.
class HandlerChainBuilder
def initialize
@handlers = []
end
def add_validation(validator_class, *args)
@handlers << validator_class.new(*args)
self
end
def add_transformation(transformer_class, *args)
@handlers << transformer_class.new(*args)
self
end
def add_custom(handler)
@handlers << handler
self
end
def build
return nil if @handlers.empty?
@handlers.each_cons(2) do |current, next_handler|
current.next_handler = next_handler
end
@handlers.first
end
end
# Usage
chain = HandlerChainBuilder.new
.add_validation(LengthValidator, 5)
.add_validation(FormatValidator, /^\w+$/)
.add_transformation(UppercaseTransformer)
.add_custom(CustomHandler.new)
.build
Practical Examples
Expense Approval System: Organizations often route expense requests through multiple approval levels based on amount and type.
class ExpenseRequest
attr_accessor :amount, :type, :description, :approved_by
def initialize(amount, type, description)
@amount = amount
@type = type
@description = description
@approved_by = []
end
end
class ApprovalHandler
attr_accessor :next_handler
def approve(request)
if can_approve?(request)
process_approval(request)
elsif @next_handler
@next_handler.approve(request)
else
reject_request(request)
end
end
protected
def can_approve?(request)
raise NotImplementedError
end
def process_approval(request)
raise NotImplementedError
end
def reject_request(request)
raise ApprovalError, "No authority to approve #{request.type} expense of #{request.amount}"
end
end
class TeamLeadApprover < ApprovalHandler
def can_approve?(request)
request.amount <= 500
end
def process_approval(request)
request.approved_by << 'Team Lead'
puts "Team Lead approved #{request.amount} for #{request.description}"
true
end
end
class ManagerApprover < ApprovalHandler
def can_approve?(request)
request.amount <= 5000
end
def process_approval(request)
request.approved_by << 'Manager'
puts "Manager approved #{request.amount} for #{request.description}"
true
end
end
class DirectorApprover < ApprovalHandler
def can_approve?(request)
request.amount <= 50000
end
def process_approval(request)
request.approved_by << 'Director'
puts "Director approved #{request.amount} for #{request.description}"
true
end
end
class CFOApprover < ApprovalHandler
def can_approve?(request)
true # CFO can approve any amount
end
def process_approval(request)
request.approved_by << 'CFO'
puts "CFO approved #{request.amount} for #{request.description}"
true
end
end
# Setup approval chain
team_lead = TeamLeadApprover.new
manager = ManagerApprover.new
director = DirectorApprover.new
cfo = CFOApprover.new
team_lead.next_handler = manager
manager.next_handler = director
director.next_handler = cfo
# Process various requests
small_expense = ExpenseRequest.new(300, 'office_supplies', 'Printer paper')
team_lead.approve(small_expense)
# => Team Lead approved 300 for Printer paper
large_expense = ExpenseRequest.new(25000, 'equipment', 'Server upgrade')
team_lead.approve(large_expense)
# => Director approved 25000 for Server upgrade
Logging System with Multiple Destinations: Applications often route log messages to different destinations based on severity level.
class LogMessage
attr_reader :level, :message, :timestamp, :context
LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }
def initialize(level, message, context = {})
@level = level
@message = message
@timestamp = Time.now
@context = context
end
def level_value
LEVELS[@level]
end
end
class LogHandler
attr_accessor :next_handler
attr_reader :threshold
def initialize(threshold)
@threshold = threshold
end
def log(message)
write_log(message) if should_log?(message)
@next_handler&.log(message)
end
protected
def should_log?(message)
message.level_value >= LogMessage::LEVELS[@threshold]
end
def write_log(message)
raise NotImplementedError
end
end
class ConsoleLogger < LogHandler
def write_log(message)
puts "[#{message.timestamp}] #{message.level.upcase}: #{message.message}"
end
end
class FileLogger < LogHandler
def initialize(threshold, filepath)
super(threshold)
@filepath = filepath
end
def write_log(message)
File.open(@filepath, 'a') do |file|
file.puts "#{message.timestamp}|#{message.level}|#{message.message}|#{message.context.inspect}"
end
end
end
class EmailLogger < LogHandler
def initialize(threshold, recipients)
super(threshold)
@recipients = recipients
end
def write_log(message)
# Send email to recipients
subject = "Application #{message.level.upcase}: #{message.message[0..50]}"
body = "Time: #{message.timestamp}\nLevel: #{message.level}\nMessage: #{message.message}\nContext: #{message.context.inspect}"
send_email(@recipients, subject, body)
end
private
def send_email(recipients, subject, body)
puts "Sending email to #{recipients.join(', ')}: #{subject}"
end
end
class MetricsLogger < LogHandler
def initialize(threshold, metrics_service)
super(threshold)
@metrics_service = metrics_service
end
def write_log(message)
@metrics_service.increment("log.#{message.level}")
@metrics_service.record("log.latency", Time.now - message.timestamp)
end
end
# Setup logging chain
console = ConsoleLogger.new(:debug)
file = FileLogger.new(:info, 'application.log')
email = EmailLogger.new(:error, ['ops@example.com'])
metrics = MetricsLogger.new(:warn, MetricsService.new)
console.next_handler = file
file.next_handler = metrics
metrics.next_handler = email
# Usage
logger = console
logger.log(LogMessage.new(:debug, 'Processing user request'))
# => [2025-01-15 10:30:00] DEBUG: Processing user request
logger.log(LogMessage.new(:error, 'Database connection failed', database: 'users'))
# => Logs to console, file, metrics, and sends email
HTTP Request Processing Pipeline: Web applications process incoming requests through multiple stages including authentication, rate limiting, and caching.
class HTTPRequest
attr_accessor :path, :method, :headers, :body, :user
attr_reader :metadata
def initialize(path, method, headers = {}, body = nil)
@path = path
@method = method
@headers = headers
@body = body
@metadata = {}
end
end
class HTTPHandler
attr_accessor :next_handler
def handle(request)
raise NotImplementedError
end
end
class CORSHandler < HTTPHandler
def handle(request)
request.headers['Access-Control-Allow-Origin'] = '*'
request.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
request.metadata[:cors_applied] = true
@next_handler&.handle(request)
end
end
class RateLimitHandler < HTTPHandler
def initialize(limit, window)
@limit = limit
@window = window
@requests = Hash.new { |h, k| h[k] = [] }
end
def handle(request)
client_id = request.headers['X-Client-ID'] || request.headers['REMOTE_ADDR']
current_time = Time.now
@requests[client_id].reject! { |time| current_time - time > @window }
if @requests[client_id].size >= @limit
raise RateLimitError, "Rate limit exceeded: #{@limit} requests per #{@window} seconds"
end
@requests[client_id] << current_time
request.metadata[:rate_limit_remaining] = @limit - @requests[client_id].size
@next_handler&.handle(request)
end
end
class AuthenticationHandler < HTTPHandler
def initialize(auth_service)
@auth_service = auth_service
end
def handle(request)
token = request.headers['Authorization']&.sub(/^Bearer /, '')
if token.nil? || token.empty?
raise AuthenticationError, 'Missing authentication token'
end
user = @auth_service.validate_token(token)
if user.nil?
raise AuthenticationError, 'Invalid authentication token'
end
request.user = user
request.metadata[:authenticated] = true
@next_handler&.handle(request)
end
end
class CacheHandler < HTTPHandler
def initialize(cache)
@cache = cache
end
def handle(request)
return cached_response(request) if cacheable?(request)
@next_handler&.handle(request)
end
private
def cacheable?(request)
request.method == 'GET' && !request.headers['Cache-Control']&.include?('no-cache')
end
def cached_response(request)
cache_key = "#{request.method}:#{request.path}"
cached = @cache.get(cache_key)
if cached
request.metadata[:cache_hit] = true
return cached
end
response = @next_handler&.handle(request)
@cache.set(cache_key, response, ttl: 300) if response
request.metadata[:cache_miss] = true
response
end
end
class BusinessLogicHandler < HTTPHandler
def handle(request)
# Actual business logic processing
{
status: 200,
body: { message: "Processed #{request.path}" },
metadata: request.metadata
}
end
end
# Build request processing pipeline
cors = CORSHandler.new
rate_limit = RateLimitHandler.new(100, 60)
auth = AuthenticationHandler.new(AuthService.new)
cache = CacheHandler.new(Cache.new)
business = BusinessLogicHandler.new
cors.next_handler = rate_limit
rate_limit.next_handler = auth
auth.next_handler = cache
cache.next_handler = business
# Process request
request = HTTPRequest.new('/api/users', 'GET', {
'Authorization' => 'Bearer valid_token_here',
'X-Client-ID' => 'client_123'
})
response = cors.handle(request)
Common Patterns
Early Termination Pattern: Handlers that detect terminal conditions stop the chain immediately rather than passing the request forward.
class ValidationChain
class Handler
attr_accessor :next_handler
def validate(data)
errors = check_errors(data)
return { valid: false, errors: errors } unless errors.empty?
if @next_handler
@next_handler.validate(data)
else
{ valid: true, errors: [] }
end
end
protected
def check_errors(data)
[]
end
end
class RequiredFieldsValidator < Handler
def initialize(required_fields)
@required_fields = required_fields
end
def check_errors(data)
@required_fields.select { |field| data[field].nil? }
.map { |field| "#{field} is required" }
end
end
class TypeValidator < Handler
def initialize(type_mappings)
@type_mappings = type_mappings
end
def check_errors(data)
@type_mappings.map do |field, expected_type|
value = data[field]
next if value.nil?
"#{field} must be #{expected_type}" unless value.is_a?(expected_type)
end.compact
end
end
end
Request Transformation Pattern: Each handler modifies the request before passing it forward, building up the final result through successive transformations.
class TransformationPipeline
def initialize
@transformers = []
end
def add(transformer)
@transformers << transformer
self
end
def transform(data)
@transformers.reduce(data) do |current_data, transformer|
transformer.call(current_data)
end
end
end
# Usage with data processing
pipeline = TransformationPipeline.new
.add(->(data) { data.merge(normalized: data[:value].downcase) })
.add(->(data) { data.merge(trimmed: data[:normalized].strip) })
.add(->(data) { data.merge(validated: data[:trimmed].length > 0) })
.add(->(data) { data.merge(final: data[:validated] ? data[:trimmed] : nil) })
result = pipeline.transform(value: ' HELLO ')
# => { value: ' HELLO ', normalized: ' hello ', trimmed: 'hello', validated: true, final: 'hello' }
Priority-Based Handling: Handlers have associated priorities, and the chain routes requests to handlers based on priority order.
class PriorityHandler
attr_reader :priority
attr_accessor :next_handler
def initialize(priority)
@priority = priority
end
def handle(request)
result = try_handle(request)
return result unless result.nil?
@next_handler&.handle(request)
end
protected
def try_handle(request)
nil
end
end
class PriorityChain
def initialize
@handlers = []
end
def add_handler(handler)
@handlers << handler
rebuild_chain
self
end
def handle(request)
@chain_head&.handle(request)
end
private
def rebuild_chain
sorted = @handlers.sort_by { |h| -h.priority }
sorted.each_cons(2) do |current, next_handler|
current.next_handler = next_handler
end
@chain_head = sorted.first
end
end
Parallel Chain Pattern: Request broadcasts to multiple handlers simultaneously, collecting results from all handlers.
class ParallelChain
def initialize
@handlers = []
end
def add_handler(handler)
@handlers << handler
self
end
def handle(request)
results = @handlers.map do |handler|
begin
handler.handle(request)
rescue => e
{ error: e.message, handler: handler.class.name }
end
end
{
request: request,
results: results,
success_count: results.count { |r| !r.is_a?(Hash) || !r.key?(:error) }
}
end
end
class NotificationHandler
def handle(message)
# Send notification
{ sent: true, channel: self.class.name }
end
end
class EmailNotification < NotificationHandler; end
class SMSNotification < NotificationHandler; end
class PushNotification < NotificationHandler; end
# Broadcast to all notification channels
notifications = ParallelChain.new
.add_handler(EmailNotification.new)
.add_handler(SMSNotification.new)
.add_handler(PushNotification.new)
result = notifications.handle(message: 'Important update')
Conditional Chain Pattern: Handler selection based on request properties determines which chain processes the request.
class ConditionalRouter
def initialize
@routes = []
end
def add_route(condition, handler)
@routes << { condition: condition, handler: handler }
self
end
def handle(request)
route = @routes.find { |r| r[:condition].call(request) }
if route
route[:handler].handle(request)
else
raise RoutingError, "No handler found for request: #{request.inspect}"
end
end
end
# Route based on request type
router = ConditionalRouter.new
.add_route(->(req) { req[:type] == :payment }, PaymentChain.new)
.add_route(->(req) { req[:type] == :refund }, RefundChain.new)
.add_route(->(req) { req[:type] == :query }, QueryChain.new)
.add_route(->(req) { true }, DefaultChain.new) # Catch-all
Reference
Core Components
| Component | Responsibility | Implementation |
|---|---|---|
| Handler Interface | Defines request handling contract | Abstract class or module with handle method |
| Concrete Handler | Implements specific handling logic | Subclass implementing can_handle? and process |
| Next Handler Reference | Links to successor in chain | attr_accessor or instance variable |
| Chain Builder | Constructs and links handlers | Utility class for chain assembly |
| Client | Initiates requests | Object that calls first handler |
Handler Responsibilities
| Responsibility | Description | Implementation Approach |
|---|---|---|
| Request Evaluation | Determine if handler can process request | Implement can_handle? method |
| Request Processing | Execute handling logic | Implement process method |
| Delegation Decision | Choose to handle or forward | Conditional logic in handle method |
| Chain Termination | Stop propagation when appropriate | Return without calling next handler |
| Error Handling | Manage errors during processing | Begin-rescue blocks or error handlers |
Chain Configuration Patterns
| Pattern | Use Case | Implementation |
|---|---|---|
| Static Chain | Fixed handler sequence | Link handlers at initialization |
| Dynamic Chain | Runtime handler composition | Build chain based on request properties |
| Conditional Chain | Route to different chains | Router selects chain based on conditions |
| Priority Chain | Order by handler priority | Sort handlers before linking |
| Parallel Chain | Broadcast to multiple handlers | Execute all handlers, collect results |
Common Handler Types
| Handler Type | Purpose | Example Use Case |
|---|---|---|
| Validator | Check request validity | Input validation, business rule checking |
| Transformer | Modify request data | Data normalization, format conversion |
| Authenticator | Verify credentials | User authentication, token validation |
| Authorizer | Check permissions | Role-based access control |
| Logger | Record processing information | Audit logging, debugging |
| Cache | Store and retrieve results | Response caching, memoization |
| Rate Limiter | Control request frequency | API throttling, DDoS prevention |
| Error Handler | Process failures | Error recovery, fallback logic |
Request Flow Control
| Control Mechanism | Effect | Implementation |
|---|---|---|
| Process and Stop | Handle request, prevent forwarding | Return result without calling next |
| Process and Continue | Handle request, allow forwarding | Call next handler after processing |
| Skip and Forward | Don't handle, pass to next | Call next handler immediately |
| Reject Request | Terminate with error | Raise exception or return error |
| Broadcast | Send to multiple handlers | Iterate all handlers in parallel |
Anti-Patterns
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Infinite Loop | Handler forwards to itself | Ensure acyclic chain structure |
| Missing Termination | No handler processes request | Add default handler at chain end |
| Tight Coupling | Handlers depend on specific handlers | Depend only on handler interface |
| Overly Long Chains | Performance degradation | Break into smaller chains or optimize |
| Side Effect Ordering | Handlers make assumptions about order | Make handlers order-independent |
| Handler Mutation | Handlers modify each other | Keep handlers isolated and immutable |
Performance Characteristics
| Aspect | Characteristic | Optimization Strategy |
|---|---|---|
| Time Complexity | O(n) worst case | Place likely handlers early |
| Space Complexity | O(n) for chain | Reuse handlers across chains |
| Handler Overhead | Per-handler invocation cost | Minimize handler logic |
| Request Copying | Potential object duplication | Pass by reference when safe |
| Chain Construction | One-time setup cost | Build chains at initialization |
Testing Strategies
| Strategy | Focus | Approach |
|---|---|---|
| Unit Testing | Individual handler logic | Test handlers in isolation |
| Integration Testing | Chain behavior | Test full chain execution |
| Mock Successors | Handler delegation | Mock next handler |
| Edge Case Testing | Boundary conditions | Test empty chains, single handlers |
| Error Scenario Testing | Failure handling | Test exception propagation |
| Performance Testing | Chain efficiency | Benchmark with various chain lengths |