Applying the Chain of Responsibility Pattern for Product Validation and Workflow Management
This article explains the Chain of Responsibility design pattern, demonstrates its use in multi‑step product validation and expense‑approval workflows with concrete Java examples, shows how to configure and assemble handlers dynamically, and discusses the pattern's advantages and drawbacks.
The Chain of Responsibility pattern assembles a series of processing objects into a chain, where each node can handle a request or pass it to the next node.
Application scenarios include multi‑step validation (e.g., creating a product) and workflow processing (e.g., expense reimbursement).
Case 1 – Product creation validation : the creation process is split into three steps – create product, validate parameters, and save product. Validation itself consists of several checks (null values, price, stock) that form a pipeline. The following simplified data object is used:
@Data
@Builder
public class ProductVO {
private Long skuId;
private String skuName;
private String Path;
private BigDecimal price;
private Integer stock;
}An abstract handler defines the common structure:
@Component
public abstract class AbstractCheckHandler {
@Getter @Setter
protected AbstractCheckHandler nextHandler;
@Setter @Getter
protected ProductCheckHandlerConfig config;
public abstract Result handle(ProductVO param);
protected Result next(ProductVO param) {
if (Objects.isNull(nextHandler)) {
return Result.success();
}
return nextHandler.handle(param);
}
}Three concrete handlers extend this class:
@Component
public class NullValueCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
if (super.getConfig().getDown()) {
return super.next(param);
}
if (Objects.isNull(param) || Objects.isNull(param.getSkuId())
|| Objects.isNull(param.getPrice()) || Objects.isNull(param.getStock())) {
return Result.failure(ErrorCode.PARAM_NULL_ERROR);
}
return super.next(param);
}
} @Component
public class PriceCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
if (param.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
}
return super.next(param);
}
} @Component
public class StockCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
if (param.getStock() < 0) {
return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
}
return super.next(param);
}
}The client that triggers the chain is:
public class HandlerClient {
public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
Result handlerResult = handler.handle(param);
if (!handlerResult.isSuccess()) {
System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult);
return handlerResult;
}
return Result.success();
}
}Handler configuration is stored as JSON and parsed into ProductCheckHandlerConfig objects:
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
private String handler; // Spring bean name
private ProductCheckHandlerConfig next; // next node
private Boolean down = Boolean.FALSE; // downgrade flag
}The method getHandler(ProductCheckHandlerConfig config) recursively builds the chain by looking up beans from handlerMap , setting the config, and linking nextHandler . The validation flow is then invoked via HandlerClient.executeChain(handler, param) .
Case 2 – Expense‑approval workflow : a similar chain is built for three approval levels (tier‑3, tier‑2, tier‑1). Each level implements an approve() method, and the configuration defines the amount range each approver can handle. The chain proceeds until the appropriate level approves or the request is rejected.
Advantages of the pattern include decoupling of validation logic, easy extension, and dynamic re‑configuration. Drawbacks involve potential performance overhead and the risk of deep recursion causing stack overflow if not properly terminated.
Overall, the article demonstrates how to refactor monolithic validation code into a clean, reusable chain, improving maintainability and scalability in backend systems.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.