欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

异步调用解耦合

最编程 2024-02-23 08:36:58
...

在开发中我们经常会遇到如下的业务

当前单据生成完成后会使用当前单据的数据进行整理汇总后下推执行其他模块的业务。我们在遇到这种问题的时候基本上就会使用异步任务或mq的方式进行消息推送。没错,但是在消息推送之前呢?我们是不是首先要将下游业务所需的参数给封装好。比如下面的案例:

产品产出业务执行完毕后,需要调用WMS模块推送入库数据:

@Override
public Result outPutProduct() {
    // 产品产出
    PO po = new PO();
    dao.save(po);
    
    // 异步推送WMS入库数据
    asyncService.pushWMS(po);
}

@Async
public void pushWMS(PO po) {
    // 封装WMS入库数据所需要的DTO
    WMSDTO dto = new WMSDTO();
    ...
    
    // 发送请求/MQ给WMS模块
    ...
}

好,现在我们将这个需求完成了。但是过了一段时间后又来了一个需求:希望在产品产出后除了推送WMS入库数据之外,还要给质检模块推送产品数据进行质检。

一般来说如果我们为了图省事的话,是不是都这样写过:

@Override
public Result outPutProduct() {
    // 产品产出
    PO po = new PO();
    dao.save(po);

    // 异步推送WMS入库数据
    asyncService.pushWMS(po);
    // 异步推送产品质检数据
    asyncService.pushQCS(po);
}

@Async
public void pushWMS(PO po) {
    // 封装WMS入库数据所需要的DTO
    WMSDTO dto = new WMSDTO();
    ...
    // 发送请求/MQ给WMS模块
    ...
}
@Async
public void pushQCS(PO po) {
    // 封装QCS数据所需要的DTO
    QCSDTO dto = new QCSDTO();
    ...
    // 发送请求/MQ给WMS模块
    ...
}

还是说这样?

@Override
public Result outPutProduct() {
    // 产品产出
    PO po = new PO();
    dao.save(po);

    // 推送
    push(po);
}
private void push(PO po) {
    // 异步推送WMS入库数据
    asyncService.pushWMS(po);
    // 异步推送产品质检数据
    asyncService.pushQCS(po);
}

@Async
public void pushWMS(PO po) {
    // 封装WMS入库数据所需要的DTO
    WMSDTO dto = new WMSDTO();
    ...
    // 发送请求/MQ给WMS模块
    ...
}
@Async
public void pushQCS(PO po) {
    // 封装QCS数据所需要的DTO
    QCSDTO dto = new QCSDTO();
    ...
    // 发送请求/MQ给WMS模块
    ...
}

其实我觉得都不太好,毫无疑问,以上的代码中下游业务与上游业务的耦合性太强,不符合开闭原则。后期如果再加需求或者删除需求呢?我们还是要去改业务代码。

我的解决方案

那么我们可否借鉴一些Redis发布订阅来对我们模块内的方法调用进行一下解耦呢?答案当然是肯定的,要不写这个文章干嘛,哈哈!

在设计模式中有一个模式叫观察者模式。Spring也基于这个模式为我们封装了一个基于事件的发布订阅模型。当前如果要自己实现的话也一样,两者大同小异。那么下面我们就采用Spring提供的发布订阅模型对业务进行一下优化(具体的原理可以查询一下网上的资料,网上资料太多了,这里就不再赘述)。

伪代码

首先我们先定义一个事件

public class OutPutProductEvent extends ApplicationEvent {
    // 产品产出PO
    private PO po;
    public OutPutProductEvent(Object source, PO po) {
        super(source);
        this.po = po;
    }
    public String getPO() {
        return po;
    }
}

创建事件发布者

@Component
public class OutPutProductProducer {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    // 发布消息
    public void sendMessage(PO po) {
        OutPutProductEvent event = new OutPutProductEvent(this, po);
        applicationEventPublisher.publishEvent(event);
    }
}

创建事件订阅者

这里将下游业务和上游业务完全解耦,后面需要添加/减少业务业务时就不需要再动上游业务的代码。

@Component
@Async
public class OutPutProductListener {

    // 推送WMS入库数据
    @EventListener
    public void pushWMSListener(OutPutProductEvent event) {
        PO po = event.getPO();
        // 封装WMS入库数据所需要的DTO
        WMSDTO dto = new WMSDTO();
        ...
        // 发送请求/MQ给WMS模块
        ...
    }
    
    // 推送产品质检数据
    @EventListener
    public void pushQCSListener(OutPutProductEvent event) {
        PO po = event.getPO();
        // 封装QCS数据所需要的DTO
        QCSDTO dto = new QCSDTO();
        ...
        // 发送请求/MQ给WMS模块
        ...
    }
}

产出产品业务

@Override
public Result outPutProduce() {
    // 产品产出
    PO po = new PO();
    dao.save(po);

    // 发布
    outPutProductProducer.sendMessage(po);
}

那么既然上面说是借鉴Redis发布订阅实现,那为什么不直接使用Redis或MQ呢?非要借鉴?嗯,不用他俩肯定是有道理的。

Redis的发布订阅和上面的代码逻辑非常的相似,但是Redis毕竟是属于一个中间件,一个系统采用中间件来实现业务必然会伴随着系统复杂度的增加。而且还要去考虑在集群模式下分布式锁的问题,还要考虑极端情况下Redis宕机前持久化失败的问题以及后续的补偿措施。何必呢!

致谢

以上是我在工作学习中的一些总结,欢迎各位小伙伴们指正。我们一起努力、一起提升。

推荐阅读