责任链模式在数据处理的实践与思考

2022/12/27

责任链模式在数据处理的实践与思考

责任链模式

在 GOF 设计模式中的责任链模式,是通过链接多个处理器对象,来让一个输入可以有多个处理者,在 Servlet 标准中,Filter 就是责任链的一种实现:

public interface Filter {

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
}
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //...
    }
}

另有一种责任链的变体,暂且叫它责任链编排模式:

public class Chain {
    void process(){
        for(var chainNode : chain) {
            if (!chainNode.process()) {
                break;
            }
        }
    }
}

编排模式与标准责任链模式的区别在于处理器对象的串连是在处理器内部,还是由外部来进行编排,当然编排模式也无法实现类似于标准模式中 Web Filter 的对 Response,即下一个处理器节点的返回值的处理。

实践

繁杂的数据处理流程

曾接手一个需求,随着系统的演进原本的一个简单数据入库需求变得超级繁琐,在入库之前需要对数据清洗、处理、与第三方服务进行交互,原本几十行代码能完成的需求在不断膨胀,于是决定下手重构。

重构

虽说处理流程很繁琐,但整体架构是很清晰的,就是数据过来,经过一系列串行流程,然后入库。

于是把原本的一整坨代码拆分成一个个独立的 Processor,把数据存入到一个抽象为 Context 的东西中,Context 在 Processor 之间单向传递,整体设计大概这样:

interface Processor {
  boolean process(ProcessContext context);
}

ProcessContext context = new ProcessContext();
context.setData(data);
for (var processor : getChainList()) {
    if (!processor.process(context)) {
      //...throws exception
    }
}

效果

随着系统的继续演进,Processor 的数量一度膨胀到了 17 个,事实证明,这次重构避免了后续进一步需求爆炸导致的代码维护噩梦。

同时一旦有了新需求,也变得比一坨代码时更加简单,如果是对已有职责的修改,只需修改相对应的 Processor 就行了,如果是新添加的职责,则需要稍微分析一下,决定新的 Processor 应该被插到哪里。

思考

不足

此次的设计后续来看仍有一些不足:

  1. Processor 处理后的返回值只有 true 和 false 用来决定流程是否还要继续走下去,这限制了后续的扩展,整体不太灵活,如果想要跳过后续的某个 Processor,就需要围绕 Context,做一些 Workaround,很麻烦,现在来看,如果把 Context 作为一个控制点的话,扩展性跟灵活性应该会好很多,比如在 Context 提供一个层次更高的 API,比如 skipNext, skip(n), skipXX 之类的。
  2. getChainList() 使用的是通过读取配置的方式来编排 Processor,这个设计点在后续应用的线上运维并没我想象的常用,虽说感觉有点鸡肋,但也有一定的应用场景:由于系统有不同的版本在运行,所以有些系统仍在跑着老流程,但有些系统已经要跑新流程了,所以这个配置被用来做切换开关了。
  3. 逻辑分散,坦白来说这点并不是此次设计的问题,而是随着系统运行,用户不断使用而提出各种大大小小的需求调整,当时为了追求速度,没有仔细考虑,采取修修补补的方式在各个 Processor 添加着各种分散逻辑,其实是属于技术债,后来也痛定思痛,使用了外观模式作为这块特殊逻辑的统一入口,把入口剥离成一个新的 Processor,清除掉分散在各个 Processor 散乱的代码。

关于第二点,曾经还考虑过实现流程热更新,但后来发现其实现有的业务需求对停机重启并不是很敏感,而且我们的数据处理流程一旦确定下来,除了需求调整,基本就不会变了,所以这个想法就搁置了。

设计模式

最后,还是想谈谈自己对设计模式使用的看法,虽有很多劝告说不要滥用设计模式,或说最好的设计模式就是没有设计模式,但个人认为,在业务允许你犯错或者犯错后果不是很严重的情况下,作为一个编程新手,应该在自己的编码过程中尝试多用一些设计模式,就如我使用这个责任链模式是在刚入行不到半年的情况下,但这段时间是我认为我自己成长得最快的时光,通过不断的试错,找到适合自己或者适合业务的设计模式。

Post Directory