责任链、领域模型和事务的恩怨情仇

简介: 责任链、领域模型和事务的恩怨情仇

责任链模式是一种非常经典的行为型设计模式,本身比较简单,但在真实开发中,我们需要考虑领域模型,需要考虑事务,就会变得复杂起来。

1 初识责任链

「GoF」的《设计模式》中定义如下:

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

翻译过来就是,把发送者和接收者解耦,把所有接收者放在一条链上,让请求沿着这个链传递直到有一个节点处理成功。

不过,在实际开发中,这个定义多多少少有点过时了。有些场景做了改变,那就是每个节点都会处理这个请求,一直到所有节点都处理完毕,其中一个节点处理失败,就终止。

1.1 代码实现

来假设一个场景,一个电商系统要处理一笔购买请求,需要3个步骤,保存订单、账户扣款、扣减库存。我们先定义一个UML类图:

微信图片_20221212182437.png

上图定义了一个IProcessor接口,然后对订单、账户和库存分别定义了实现,这3个实现被组织成了一个责任链,入参是Apply对象,即购买请求。责任链逻辑在ProcessorChain类,代码如下

public class ProcessorChain {
    private List<IProcessor> processors = new ArrayList<>(3);
    public void addProcessor(IProcessor processor){
        processors.add(processor);
    }
    public void doProcess(Apply apply){
        processors.forEach(r -> r.process(apply));
    }
}

1.2 优点

1.2.1

使用责任链模式可以将复杂业务拆分成小的单元,让不同的开发人员关注小的单元业务。

试想如果上面订单、账户和库存在一个类里面,业务代码会非常复杂和庞大,开发人员分工协作也不太容易。

1.2.2

如果要增加一个节点,不用对前面的节点进行修改,符合开闭原则。

责任链模式的使用非常广泛,比如mybatis中的Interceptorxxljob的子任务调度等。

2 领域模型

上面责任链代码的实现有点太简单了,如果我们引入领域模型,要怎么处理呢?

领域模型是DDD中的概念,是指针对业务领域里中关键模块进行抽象,构建出软件系统中的映射模型。

比如,这里为了让业务更加清晰,我们定义了一个购物的领域模型,ShoppingModel。代码如下:

public class ShoppingModel {
    private Apply apply;
    private OrderDo orderDo;
    private AccountDo accountDo;
    private StorageDo storageDo;
    public ShoppingModel(Apply apply){
        this.apply = apply;
    }
}

ShoppingModel里面定义了订单、库存和账户的Do模型。这时上一节定义的UML类图需要修改成如下:

微信图片_20221212182508.png

这时ProcessorChain的代码变成了下面这样:

public class ProcessorChain1 {
    private List<IProcessor> processors = new ArrayList<>(3);
    public void addProcess(IProcessor processor){
        processors.add(processor);
    }
    public void doProcess(Apply apply){
        ShoppingModel shoppingModel = new ShoppingModel(apply);
        processors.forEach(r -> r.process(shoppingModel));
    }
}

我们抽象出了Do模型,下一步就要考虑DML操作了,这不得不考虑事务处理。

3 事务登场

引入领域模型后,在订单节点处理完成后,生成orderDo,账户节点处理完成后,生成accountDo,库存节点处理完成后生成storageDo

这3个Do都需要持久化到数据库,而且必须是一个原子操作,那要怎么处理呢?

3.1 doProcess方法加事务

最简单的就是在ProcessorChaindoProcess方法上加@Transactional注解,这样整个流程就在一个事务里面了。

这时问题来了,如果3个processor都有非常耗时的处理逻辑,整个事务就耗时太长了,长时间占用数据库连接资源不能释放。

3.2 加一个db节点

如果在责任链最后面加一个db节点DbProcessor,前面3个节点负责处理业务逻辑,DbProcessor节点负责持久化数据,这样整个事务就除去了业务处理花费的时间。

DbProcessor类中process代码如下:

@Transactional
@Override
public boolean process(ShoppingModel shoppingModel) {
    orderMapper.save(shoppingModel.getOrderDo());
    accountMapper.update(shoppingModel.getAccountDo());
    storageMapper.update(shoppingModel.getStorageDo());
    return true;
}

4 新的问题

但是又有新的问题,由于业务的需要,必须要在StorageProcessor节点扣减库存并落库。

如果我们在StorageProcessorprocess方法上加上@Transactional注解,并不能保证事务传播到DbProcessor,这就需要再次改造了。

我们使用责任链模式的第二种写法进行改造,UML类图如下:

微信图片_20221212182536.png

从图中我们可以看到,在4processor中,前面的processor增加了对后面processor的依赖。每个processor都需要一个指向next节点的引用。每次process处理结束后,判断一下next是否为空,如果next不为空,则调用next.process方法。

为了让代码更加简洁,我们需要作出3个修改。

4.1 引入了模板模式

模板类是AbstractProcessor,它的process方法来实现所有Processor的公共逻辑,代码如下:

public abstract class AbstractProcessor implements IProcessor {
    protected IProcessor next;
    @Override
    public boolean process(ShoppingModel shoppingModel){
        boolean result = doProcess(shoppingModel);
        if (result && null != next){
            return next.process(shoppingModel);
        }
        return result;
    }
    public void setNext(IProcessor next){
        this.next = next;
    }
    protected abstract boolean doProcess(ShoppingModel shoppingModel);
}

这样每个节点类就不需要实现IProcessorprocess方法,而是继承AbstractProcessor,实现抽象方法doProcess

4.2 ProcessorChain类

Processor类不能放在List上了,而要组织成一个链表,代码如下:

public class ProcessorChain2 {
    private IProcessor head;
    private IProcessor tail;
    public ProcessorChain2(IProcessor head){
        this.head = head;
        this.tail = head;
    }
    public void addProcess(IProcessor processor){
        tail.setNext(processor);
        this.tail = processor;
    }
    public void doProcess(ShoppingModel shoppingMode){
        head.process(shoppingMode);
    }
}

4.3 StorageProcessor控制事务

事务需要由StorageProcessor来控制,代码如下:

public class StorageProcessor extends AbstractProcessor {
    @Transactional
    @Override
    protected boolean doProcess(ShoppingModel shoppingModel) {
        storageMapper.update(shoppingModel.getStorageDo());
        return true;
    }
}

5 一些思考

5.1 领域模型

引入领域模型,把复杂业务抽象成程序实体,简化了业务,代码结构也更加清晰。并且DB节点的加入,让事务后移,减去了计算过程花费的时间。

同时,引入领域模型也会带来一些问题,比如在上面的电商购物案例中,如果每个节点都必须要有DML操作,是否还需要抽象出ShoppingModel

5.2 事务控制

我们肯定是要尽可能地降低事务的耗时。除了优化sql,程序中的处理时间也是一个优化点。是否要加DbProcessor节点,我们考虑下面几点:

  • 前面节点的计算过程是否耗时很多
  • 前面的节点是否需要DML操作
  • 事务控制加在什么地方

5.3 批量执行

如果又来一个改造,把电商系统收到的请求都缓存下来,之后批处理,又该怎么做呢?

  • 循环调用ProcessorChaindoProcess方法

业务代码改动小,但是跟数据库交互多。

  • 去掉DB节点,其他3个节点各自管理事务,事务批量提交。

改造大,需要为每个Apply记录3个处理状态。好处是跟数据库交互少,3个节点可以并行执行。

类似下面伪代码:

public class OrderProcessor extends AbstractProcessor {
    @Resource
    private OrderMapper orderMapper;
    @Transactional
    @Override
    protected boolean doProcess(List<Apply> Applys) {
        orderMapper.save(apply2Order(Applys));
        return true;
    }
    private List<Order> apply2Order(List<Apply> Applys){
        List<Order> orders = null;
        //...
        return orders;
    }
}

5.4 公共依赖

回到领域模型,如果我们不考虑批量,前面3个节点也不做DML操作,那引入一个DB节点确实是非常好的设计。

可是项目上线若干个月后,团队遇到一个问题,OrderProcessor必须引入一个公共组件,这个组件里面有DML操作。这样系统又是一个不小的改造。

6 总结

6.1

责任链模式在我们开发中使用非常多,要学会这种模式也非常容易。

6.2

在我们实际的开发过程中,用好责任链并不简单,因为我们不能脱离实际业务去考虑模式本身,下面5个方面都可能给开发人员带来不小的工作量:

  • 复杂的业务特性
  • 跟领域模型的配合
  • 对事务的处理
  • 后期需求变更
  • 公共组件引入

所以,在详细设计阶段,做好业务梳理和抽象是至关重要的。

6.3

从架构师角度讲,后期要更改框架并不难,就跟我这篇文章介绍思路类似。难的是面对复杂的业务,变动框架带来的犄角旮旯的小问题,要解决这些小问题,一线操刀的程序员压力并不会小。

相关文章
|
设计模式 Java Maven
一个注解搞定责任链,学还是不学?
在繁琐的业务流程处理中,通常采用面向过程的设计方法将流程拆分成N个步骤,每个步骤执行独立的逻辑。但是这样剥离仍然不彻底,修改其中一个步骤仍然可能影响其他步骤。在这种场景下,有一种经典的设计模式-责任链模式,可以将这些子步骤封装成独立的handler,然后通过pipeline将其串联起来。
1021 173
一个注解搞定责任链,学还是不学?
|
存储 人机交互 领域建模
领域模型随想
关于领域模型
122 0
|
设计模式 安全 uml
责任链细解:从风控链视角,探索责任链的设计与实践!
责任链是一种行为型模式。顾名思义,由多个有不同处理能力节点组成的一条执行链。当一个事件进来时,会判断当前节点是否有处理的能力,反之转入下一个节点进行处理。可以从支付的风控链这个场景,深入的理解责任链模式。
|
前端开发 JavaScript
【单子】说白了不过就是【自函子范畴】上的一个【幺半群】而已?请说人话!!
起初本瓜看到【单子】说白了不过就是【自函子范畴】上的一个【幺半群】而已?这句话的时候,还以为自己在看量子力学的量子纠缠相关内容,单子、函子、粒子、玻色子、费米子、绝绝子。。。
|
资源调度 前端开发 JavaScript
马蹄链智能合约系统开发功能需求丨MetaForce佛萨奇2.0波场链
马蹄链智能合约系统开发功能需求丨MetaForce佛萨奇2.0波场链
121 0
|
前端开发
火币链/波场链/OK链/币安链盲盒游戏开发功能版,火币链/波场链/OK链/币安链盲盒游戏系统开发(成熟及方案)
The explanation of the new retail is that individuals and enterprises, relying on the Internet, upgrade and transform the production, circulation and sales process of goods by using advanced technical means such as big data, artificial intelligence and psychological knowledge, thus reshaping the bus
绩效被打C了!谈谈「绩效考核」背后的逻辑以及潜规则
在新公司度过了一个完整的 Q3 季度,被打了绩效,也给下属打了绩效,感慨颇深。 今天就好好聊聊大厂打工人最最关心的「绩效考核」,谈谈它背后的逻辑以及潜规则,摸清楚了它,你在大厂这片丛林里才能更好的生存下去。
|
存储 SQL 关系型数据库
步步为营,剖析事务中最难的——隔离性
步步为营,剖析事务中最难的——隔离性
141 0
步步为营,剖析事务中最难的——隔离性
|
存储 算法 NoSQL
波场链/币安链/马蹄链2.0佛萨奇开发源码丨2.0佛萨奇马蹄链/币安链/波场链dapp智能合约系统开发(详情及规则)
 归档后的节点在对其他节点提供区块同步信息时,无法提供已归档的区块信息,所以在需要同步的节点选择连接的peer节点时,会只选择已归档高度比自己高度低的节点。如果是高度为1的全新节点,则只能从未归档的节点(peer)同步区块
|
区块链 计算机视觉
关于代币增发复利DAPP模式制度系统开发逻辑分析(原理概念)
关于代币增发复利DAPP模式制度系统开发逻辑分析(原理概念)
139 0