Chain of Responsibility Pattern
1. Giới Thiệu
1.1 Định Nghĩa
Chain of Responsibility là một behavioral pattern cho phép bạn chuyển các yêu cầu dọc theo một chuỗi các handlers. Khi nhận được yêu cầu, mỗi handler sẽ quyết định xử lý yêu cầu hoặc chuyển nó cho handler tiếp theo trong chuỗi.
1.2 Mục Đích
- Tránh việc ghép cặp người gửi yêu cầu với người nhận
- Cho phép nhiều đối tượng xử lý yêu cầu
- Linh hoạt trong việc thêm/bớt trách nhiệm
- Phân tán trách nhiệm xử lý
2. Cấu Trúc Cơ Bản
2.1 Logger Example
// Handler interface
public abstract class Logger {
protected LogLevel level;
protected Logger nextLogger;
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
public void logMessage(LogLevel level, String message) {
if (this.level.ordinal() <= level.ordinal()) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
protected abstract void write(String message);
}
// Concrete handlers
public class ConsoleLogger extends Logger {
public ConsoleLogger(LogLevel level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Console: " + message);
}
}
public class FileLogger extends Logger {
public FileLogger(LogLevel level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File: " + message);
}
}
public class ErrorLogger extends Logger {
public ErrorLogger(LogLevel level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error: " + message);
}
}
// Client code
public class LoggerChainDemo {
public static Logger getChainOfLoggers() {
Logger errorLogger = new ErrorLogger(LogLevel.ERROR);
Logger fileLogger = new FileLogger(LogLevel.DEBUG);
Logger consoleLogger = new ConsoleLogger(LogLevel.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
Logger loggerChain = getChainOfLoggers();
loggerChain.logMessage(LogLevel.INFO,
"This is an information.");
loggerChain.logMessage(LogLevel.DEBUG,
"This is a debug level information.");
loggerChain.logMessage(LogLevel.ERROR,
"This is an error information.");
}
}
3. Ví Dụ Thực Tế
3.1 Request Authentication & Authorization
// Base handler
public abstract class AuthenticationHandler {
protected AuthenticationHandler next;
public AuthenticationHandler setNext(
AuthenticationHandler handler) {
this.next = handler;
return handler;
}
public abstract boolean handle(Request request);
protected boolean handleNext(Request request) {
if (next == null) {
return true;
}
return next.handle(request);
}
}
// Concrete handlers
public class TokenValidationHandler
extends AuthenticationHandler {
@Override
public boolean handle(Request request) {
if (!request.hasToken()) {
throw new AuthenticationException(
"No token provided");
}
String token = request.getToken();
if (!isValidToken(token)) {
throw new AuthenticationException(
"Invalid token");
}
return handleNext(request);
}
private boolean isValidToken(String token) {
// Token validation logic
return true;
}
}
public class RoleCheckHandler
extends AuthenticationHandler {
@Override
public boolean handle(Request request) {
String role = request.getRole();
if (!hasPermission(role)) {
throw new AuthorizationException(
"Insufficient permissions");
}
return handleNext(request);
}
private boolean hasPermission(String role) {
// Permission checking logic
return true;
}
}
public class RateLimitHandler
extends AuthenticationHandler {
private final RateLimiter rateLimiter;
public RateLimitHandler(int limit) {
this.rateLimiter = RateLimiter
.create(limit);
}
@Override
public boolean handle(Request request) {
if (!rateLimiter.tryAcquire()) {
throw new RateLimitException(
"Too many requests");
}
return handleNext(request);
}
}
3.2 Payment Processing
// Base handler
public abstract class PaymentHandler {
protected PaymentHandler nextHandler;
public void setNext(PaymentHandler handler) {
this.nextHandler = handler;
}
public abstract PaymentResponse process(
PaymentRequest request);
}
// Concrete handlers
public class FraudCheckHandler extends PaymentHandler {
private final FraudDetectionService fraudService;
@Override
public PaymentResponse process(
PaymentRequest request) {
if (fraudService.isFraudulent(request)) {
return PaymentResponse.fraudulent(
"Fraudulent transaction detected");
}
return nextHandler != null ?
nextHandler.process(request) :
PaymentResponse.success();
}
}
public class BalanceCheckHandler
extends PaymentHandler {
private final AccountService accountService;
@Override
public PaymentResponse process(
PaymentRequest request) {
if (!accountService.hasBalance(
request.getAccountId(),
request.getAmount())) {
return PaymentResponse.failed(
"Insufficient funds");
}
return nextHandler != null ?
nextHandler.process(request) :
PaymentResponse.success();
}
}
public class TransactionHandler extends PaymentHandler {
private final TransactionService transactionService;
@Override
public PaymentResponse process(
PaymentRequest request) {
try {
transactionService.executeTransaction(
request);
return nextHandler != null ?
nextHandler.process(request) :
PaymentResponse.success();
} catch (Exception e) {
return PaymentResponse.failed(
"Transaction failed: " +
e.getMessage());
}
}
}
4. Spring Framework Integration
4.1 Configuration
@Configuration
public class SecurityConfig {
@Bean
public AuthenticationHandler securityChain(
TokenValidationHandler tokenHandler,
RoleCheckHandler roleHandler,
RateLimitHandler rateLimitHandler) {
tokenHandler
.setNext(roleHandler)
.setNext(rateLimitHandler);
return tokenHandler;
}
@Bean
public TokenValidationHandler tokenHandler() {
return new TokenValidationHandler();
}
@Bean
public RoleCheckHandler roleHandler() {
return new RoleCheckHandler();
}
@Bean
public RateLimitHandler rateLimitHandler() {
return new RateLimitHandler(100);
}
}
4.2 Implementation
@Service
public class SecurityService {
private final AuthenticationHandler securityChain;
public SecurityService(
AuthenticationHandler securityChain) {
this.securityChain = securityChain;
}
public boolean authenticate(Request request) {
try {
return securityChain.handle(request);
} catch (AuthenticationException |
AuthorizationException |
RateLimitException e) {
log.error("Security check failed", e);
return false;
}
}
}
@RestController
@RequestMapping("/api")
public class SecureController {
private final SecurityService securityService;
@PostMapping("/secure")
public ResponseEntity<?> secureEndpoint(
@RequestBody Request request) {
if (!securityService.authenticate(request)) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.build();
}
// Process the request
return ResponseEntity.ok().build();
}
}
5. Testing
5.1 Unit Testing
@ExtendWith(MockitoExtension.class)
class AuthenticationHandlerTest {
@Mock
private TokenValidationHandler tokenHandler;
@Mock
private RoleCheckHandler roleHandler;
@Mock
private RateLimitHandler rateLimitHandler;
private Request request;
@BeforeEach
void setUp() {
request = new Request();
request.setToken("valid-token");
request.setRole("ADMIN");
tokenHandler.setNext(roleHandler);
roleHandler.setNext(rateLimitHandler);
}
@Test
void whenValidRequest_thenSuccess() {
// Given
when(tokenHandler.handle(request))
.thenReturn(true);
when(roleHandler.handle(request))
.thenReturn(true);
when(rateLimitHandler.handle(request))
.thenReturn(true);
// When
boolean result = tokenHandler.handle(request);
// Then
assertTrue(result);
verify(tokenHandler).handle(request);
verify(roleHandler).handle(request);
verify(rateLimitHandler).handle(request);
}
@Test
void whenInvalidToken_thenThrowException() {
// Given
request.setToken(null);
// When/Then
assertThrows(AuthenticationException.class,
() -> tokenHandler.handle(request));
}
}
5.2 Integration Testing
@SpringBootTest
class SecurityServiceIntegrationTest {
@Autowired
private SecurityService securityService;
@Test
void whenValidRequest_thenAuthenticate() {
// Given
Request request = new Request();
request.setToken("valid-token");
request.setRole("USER");
// When
boolean result = securityService
.authenticate(request);
// Then
assertTrue(result);
}
@Test
void whenRateLimitExceeded_thenFail() {
// Given
Request request = new Request();
request.setToken("valid-token");
request.setRole("USER");
// When
IntStream.range(0, 101).forEach(i ->
securityService.authenticate(request));
boolean result = securityService
.authenticate(request);
// Then
assertFalse(result);
}
}
6. Lợi Ích và Nhược Điểm
6.1 Lợi Ích
- Giảm sự phụ thuộc giữa các thành phần
- Linh hoạt trong việc thêm/xóa trách nhiệm
- Tuân thủ Single Responsibility Principle
- Dễ dàng mở rộng và bảo trì
- Tăng tính tái sử dụng code
6.2 Nhược Điểm
- Không đảm bảo yêu cầu được xử lý
- Có thể tạo ra chuỗi quá dài
- Debugging khó khăn hơn
- Có thể ảnh hưởng đến hiệu suất
7. Best Practices
7.1 Base Handler Template
public abstract class BaseHandler<T> {
protected BaseHandler<T> next;
protected String handlerName;
protected BaseHandler(String name) {
this.handlerName = name;
}
public BaseHandler<T> setNext(BaseHandler<T> next) {
this.next = next;
return next;
}
public abstract boolean handle(T request);
protected boolean handleNext(T request) {
if (next == null) {
return true;
}
try {
return next.handle(request);
} catch (Exception e) {
log.error("{} failed: {}",
next.handlerName, e.getMessage());
return false;
}
}
}
7.2 Builder Pattern Integration
public class HandlerChainBuilder<T> {
private BaseHandler<T> first;
private BaseHandler<T> last;
public HandlerChainBuilder<T> add(
BaseHandler<T> handler) {
if (first == null) {
first = handler;
last = handler;
} else {
last.setNext(handler);
last = handler;
}
return this;
}
public BaseHandler<T> build() {
return first;
}
}
// Usage
HandlerChainBuilder<Request> builder =
new HandlerChainBuilder<>();
BaseHandler<Request> chain = builder
.add(new TokenValidationHandler())
.add(new RoleCheckHandler())
.add(new RateLimitHandler(100))
.build();
8. Common Issues và Solutions
8.1 Error Handling
public class ChainException extends RuntimeException {
private final String handlerName;
private final String errorCode;
public ChainException(
String handlerName,
String errorCode,
String message) {
super(message);
this.handlerName = handlerName;
this.errorCode = errorCode;
}
}
public abstract class ErrorHandlingHandler<T>
extends BaseHandler<T> {
protected ErrorHandlingHandler(String name) {
super(name);
}
@Override
public boolean handle(T request) {
try {
return handleRequest(request);
} catch (Exception e) {
throw new ChainException(
handlerName,
getErrorCode(e),
e.getMessage());
}
}
protected abstract boolean handleRequest(T request);
protected abstract String getErrorCode(Exception e);
}
8.2 Performance Monitoring
public class MonitoringHandler<T>
extends BaseHandler<T> {
private final MetricRegistry metrics;
public MonitoringHandler(
String name, MetricRegistry metrics) {
super(name);
this.metrics = metrics;
}
@Override
public boolean handle(T request) {
Timer.Context context = metrics
.timer(handlerName)
.time();
try {
boolean result = handleNext(request);
metrics.counter(handlerName + ".success")
.inc();
return result;
} catch (Exception e) {
metrics.counter(handlerName + ".error")
.inc();
throw e;
} finally {
context.stop();
}
}
}
9. References
9.1 Books
- Design Patterns: Elements of Reusable Object-Oriented Software
- Head First Design Patterns
- Clean Architecture by Robert C. Martin
- Patterns of Enterprise Application Architecture