前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >责任链、领域模型和事务的恩怨情仇

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

作者头像
jinjunzhu
发布2022-08-23 16:48:38
3100
发布2022-08-23 16:48:38
举报
文章被收录于专栏:个人开发个人开发

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

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类图:

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

代码语言:javascript
复制
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。代码如下:

代码语言:javascript
复制
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类图需要修改成如下:

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

代码语言:javascript
复制
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代码如下:

代码语言:javascript
复制
@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类图如下:

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

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

4.1 引入了模板模式

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

代码语言:javascript
复制
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上了,而要组织成一个链表,代码如下:

代码语言:javascript
复制
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来控制,代码如下:

代码语言:javascript
复制
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个节点可以并行执行。 ❞

类似下面伪代码:

代码语言:javascript
复制
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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 君哥聊技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 初识责任链
    • 1.1 代码实现
      • 1.2 优点
        • 1.2.1
        • 1.2.2
    • 2 领域模型
    • 3 事务登场
      • 3.1 doProcess方法加事务
        • 3.2 加一个db节点
        • 4 新的问题
          • 4.1 引入了模板模式
            • 4.2 ProcessorChain类
              • 4.3 StorageProcessor控制事务
              • 5 一些思考
                • 5.1 领域模型
                  • 5.2 事务控制
                    • 5.3 批量执行
                      • 5.4 公共依赖
                        • 6.1
                        • 6.2
                        • 6.3
                    • 6 总结
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档