关于处理某一个事件需要关联多个事件或表的情况下,一些思考

时间:2022-07-21 22:35:05

这个场景是非常常见,毕竟纯粹的单表的CRUD比较少,大部分时候都是操作了某个表、某个业务,然后需要多个表进行更改。

譬如社交信息流类的,我发了一篇帖子,首先UserPost表需要添加一条数据,然后可能需要给关注我的人的信息流里也插一条数据,再做一些推送类的事件等等可能要很多步骤。

像电商类的下单之类的操作关联的表就更多了。

这里必然会涉及的问题就是业务代码耦合,总不能我添加了一篇帖子,然后就在帖子保存之后,再去操作N个其他的表。首先我们想到的就是业务的单一职责,譬如PostService里,就只能操作Post的增删改查,而不能再去做其他表的操作,里面也最好不要出现别的业务类的Service调用。

说到这里,就得提一下之前的一篇文章,贫血,充血模型的解释以及一些经验(http://blog.csdn.net/tianyaleixiaowu/article/details/75416209)。这个文章介绍的模型是比较有意义的。大概是说,传统的Controller,Service,Dao这种单薄的3层结构是不合适的,尤其是Service会不可避免的处理N多表的逻辑,会产生大量的重复代码。他的解决方案是将每个表做一个单薄的Manager管理类,只处理自己表的CRUD。然后对于要处理多个表的业务逻辑,再去定义一个相应的Service,在这个Service里去调用各个单表的Manager。在Controller里,应根据需要来使用Manager或者Service。

需要注意,如果你无法界定单表的界限,就是那种类里也关联了别的类,请将类里关联的类改成被关联类的Id,而不是去定义这个对象。这一点尤其是对使用hibernate来说,尽量不要去定义一个类关联,而是使用对方的Id,并为Id加上索引。而且尽量避免使用外键,请参考阿里巴巴Java手册。当项目变大,你会被外键搞的崩溃。不要贪图级联查询时的方便,来为项目变大后的巨大麻烦买单。

回归正题,怎么去做在处理某一个事件时,还需要处理N多别的事件,而又不让代码耦合进来。

说两种方案:

1.采用Spring的接口注解功能

spring有一个功能是,你在Autowired一个接口集合时,它会自动把该接口的实现类都注入进来。

譬如我要保存一个Post,那么我定义一个PostAddCallBack接口,里面有个方法void postAdd(Post post)即可。

然后所有需要监听Post新增的业务类都去实现PostAddCallBack接口即可,并在接口方法里做自己的业务。将来不需要监听了,就删除实现该接口即可,这样系统就成为了一个可插拔式的,想监听哪个事件就去实现哪个事件的接口,而不用去找该事件的触发源,不去和触发源代码耦合。

对于Post的add就像下面的写法

/**
* Created by wuwf on 17/4/20.
*/
@Service
public class PostService{
@Autowired
private PostRepository postRepository;

@Autowired
private List<PostAddCallBack> postAddCallBackList;

public Post findById(int id) {
return postRepository.findById(id);
}

public Post save(Post post) {
Post temp = postRepository.save(post);
postAddCallBackList.forEach(postAddCallBack -> postAddCallBack.postAdd(temp));
return temp;
}

}
这样就能保持单表业务的单一性。而且便于事务管理。需要注意,如果该接口没有任何实现类,在forEach会报错。

这种方式适合于单体应用,如果事件需要被别的工程监听,那自然是用不了这接口了,就需要借助于消息队列。

2.采用消息队列

消息队列一般有点对点模式、发布订阅模式,譬如阿里的ons,我们可以采用订阅模式来完成需求。

订阅模式就是有多个客户端订阅某个事件,当事件被触发后,每个客户端都能接收到该事件。

很明显消息队列适合于完成分布式环境下的消息订阅,可以在多个不同的项目间进行事件共享,问题也很明显,就是分布式事务。至于分布式事务,就是另外的事了,比较麻烦,如果不是强实时性业务,考虑使用最终一致性即可。