Table of contents
One Design Pattern a Week: Week 4
Software Engineer
Software Engineer
Design Pattern
Design Pattern
Welcome back to my "One Design Pattern a Week" series!
Try to solve this real problem: Request Handling
Imagine you're building an enterprise application that needs to process different types of requests (like approvals, validations, or authentications). Each request might need to go through multiple processing steps, and different requests might require different combinations of these steps.
Here are some potential issues:
Here are some potential issues:
- Complex Decision Logic: Without proper structure, the code becomes filled with nested if-else statements
- Tight Coupling: Processing steps are tightly coupled, making it difficult to add or remove steps
- Code Maintainability: Changes to the processing logic require modifying existing code
Take a moment to think about how you would solve this problem. How can you create a flexible system that can handle different types of requests with varying processing requirements?
An example of bad design
// language: ruby class RequestProcessor def process_request(request) # Check authentication if !authenticate(request) return "Authentication failed" end # Validate request if !validate(request) return "Validation failed" end # Check authorization if !authorize(request) return "Authorization failed" end # Process the request "Request processed successfully" end private def authenticate(request) # Authentication logic true end def validate(request) # Validation logic true end def authorize(request) # Authorization logic true end end # Usage processor = RequestProcessor.new puts processor.process_request("some request")
Issues with this implementation:
- Rigid Structure: The processing steps are hardcoded and cannot be easily modified
- Single Responsibility Principle Violation: The RequestProcessor class handles multiple responsibilities
- Poor Extensibility: Adding new processing steps requires modifying existing code
Chain of Responsibility Pattern
The Chain of Responsibility pattern is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process it or to pass it along the chain.
Key Characteristics:
Key Characteristics:
- Decoupling: Separates senders of requests from receivers
- Dynamic Chain: Handlers can be added or removed dynamically
- Single Responsibility: Each handler focuses on one specific task
Implementing Chain of Responsibility in Ruby:
// language: ruby # Abstract Handler class Handler attr_writer :next_handler def handle(request) return @next_handler.handle(request) if @next_handler nil end end # Concrete Handlers class AuthenticationHandler < Handler def handle(request) if authenticate(request) puts "Authentication successful" super(request) else "Authentication failed" end end private def authenticate(request) # Authentication logic true end end class ValidationHandler < Handler def handle(request) if validate(request) puts "Validation successful" super(request) else "Validation failed" end end private def validate(request) # Validation logic true end end class AuthorizationHandler < Handler def handle(request) if authorize(request) puts "Authorization successful" super(request) else "Authorization failed" end end private def authorize(request) # Authorization logic true end end # Client code authentication = AuthenticationHandler.new validation = ValidationHandler.new authorization = AuthorizationHandler.new # Set up the chain authentication.next_handler = validation validation.next_handler = authorization # Process request result = authentication.handle("some request") puts result
Benefits of this implementation:
- Flexible Chain Construction: Handlers can be arranged in any order
- Easy to Add/Remove Steps: New handlers can be added without modifying existing code
- Single Responsibility: Each handler focuses on one specific task
- Loose Coupling: Handlers are independent of each other
More Practical Examples
1. HTTP Request Middleware Chain
// language: ruby # Base Middleware Handler class Middleware attr_writer :next_handler def handle(request) return @next_handler.handle(request) if @next_handler request end end # CORS Middleware class CORSMiddleware < Middleware def handle(request) # Add CORS headers request.headers['Access-Control-Allow-Origin'] = '*' request.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE' puts "CORS headers added to request" super(request) end end # Authentication Middleware class JWTAuthMiddleware < Middleware def handle(request) token = request.headers['Authorization'] if token.nil? return { status: 401, message: 'No token provided' } end begin decoded_token = JWT.decode(token, ENV['JWT_SECRET'], true, algorithm: 'HS256') request.user = decoded_token['user'] puts "User authenticated successfully" super(request) rescue JWT::DecodeError { status: 401, message: 'Invalid token' } end end end # Rate Limiting Middleware class RateLimitMiddleware < Middleware def initialize @redis = Redis.new @max_requests = 100 # 100 requests @time_window = 3600 # per hour end def handle(request) client_ip = request.ip current_count = @redis.get(client_ip).to_i if current_count >= @max_requests return { status: 429, message: 'Rate limit exceeded' } end @redis.multi do @redis.incr(client_ip) @redis.expire(client_ip, @time_window) end super(request) end end # Usage class Request attr_accessor :headers, :ip, :user def initialize @headers = {} @ip = '127.0.0.1' end end # Set up middleware chain rate_limit = RateLimitMiddleware.new cors = CORSMiddleware.new auth = JWTAuthMiddleware.new rate_limit.next_handler = cors cors.next_handler = auth # Process request request = Request.new request.headers['Authorization'] = 'valid.jwt.token' result = rate_limit.handle(request)
Conclusion
The Chain of Responsibility pattern provides a flexible and maintainable way to process requests through a series of handlers. It promotes loose coupling, single responsibility, and easy modification of processing chains.
Happy coding!
Happy coding!
Created at
2024-10-28 16:46:15 +0700
Related blogs
How Google achieves seamless SSO across multiple domains like Gmail and Youtube?
Hey there! Ever wondered how you can log into Gmail and then magically find yourself logged into YouTube, Google Drive, and all other Google services ...
Software Engineer
Software Engineer
2024-09-24 22:52:06 +0700
How to customize YJIT in the Rails app
In this post, we'll dive into what YJIT is, how to enable it in your Rails app, and how to monitor and configure it for optimal performance.What is YJ...
Software Engineer
Software Engineer
2024-12-04 16:29:15 +0700