本专栏 将通过以下几块内容来搭建一个 模块化:可以根据项目的功能需求和体量进行任意模块的组合或扩展 的后端服务
项目结构与模块化构建思路
RESTful与API设计&管理
网关路由模块化支持与条件配置
DDD领域驱动设计与业务模块化(概念与理解)
DDD领域驱动设计与业务模块化(落地与实现)
DDD领域驱动设计与业务模块化(薛定谔模型)
DDD领域驱动设计与业务模块化(优化与重构)
RPC模块化设计与分布式事务
v2.0:项目结构优化升级
v2.0:项目构建+代码生成「插件篇」
v2.0:扩展模块实现技术解耦
v2.0:结合DDD与MVC的中庸之道(启发与思路)
v2.0:结合DDD与MVC的中庸之道(标准与实现)
v2.0:结合DDD与MVC的中庸之道(优化与插件)(本文)
未完待续......
在之前的文章 v2.0:项目结构优化升级 中,我们将项目的结构进行了优化,优化之后的项目结构如下
简单说明
考虑到可能有读者是第一次看这个专栏,所以我还是先简单介绍一下,详细的内容大家可以看我之前的专栏文章
这里模块化的设想其实就是可插拔的功能模块,如果需要这个功能,就把这个模块用Gradle
或Maven
的方式编译进来,如果不需要,去掉对应的依赖就行了,避免改动代码,因为一旦涉及到代码改动的话,就会变得“改不断,理还乱”
当然了,需要达到每个模块都能够任意拆卸的程度其实并不简单,所以我借鉴了DDD
的思想来达成这个目的,专栏中也有这一块的内容,今天这篇文章就是对DDD
的进一步优化
当我们把所有模块都编译进来的时候,那么就是一个单体应用,如果把多个模块分开编译,那么就变成了微服务
以我们juejin
项目的三个模块(用户,沸点,通知)为例:
在最开始的时候,我们可以将这三个模块打包成一个服务,作为单体应用来运行,适合项目前期体量较小的情况
之后,当我们发现沸点的流量越来越大,就可以将沸点模块拆分出来作为单独的一个服务方便扩容,用户模块和通知模块打包在一起作为另一个服务,这样就从单体应用变成了微服务
注:从单体应用到微服务的切换是不需要修改代码的
领域模型嵌套查询
在上一篇文章结合DDD与MVC的中庸之道(标准与实现)中我们的用户存储是这样实现的
//基于 MBP 的用户存储实现
//UserPO 为数据库表对应的数据模型
@Repository
public class MBPUserRepository extends MBPDomainRepository implements UserRepository {
@Autowired
private UserMapper userMapper;
@Autowired
private DomainValidator validator;
@Override
public UserPO do2po(User user) {
UserPO po = new UserPO();
po.setId(user.getId());
po.setUsername(user.getUsername());
po.setPassword(user.getPassword());
po.setNickname(user.getNickname());
po.setCreateTime(user.getCreateTime());
return po;
}
@Override
public User po2do(UserPO po) {
return new UserImpl.Builder()
.id(po.getId())
.username(po.getUsername())
.password(po.getPassword())
.nickname(po.getNickname())
.createTime(po.getCreateTime())
.build(validator);
}
@Override
public BaseMapper getBaseMapper() {
return userMapper;
}
}
对于简单的模型来说没什么问题
我们只要将查询出来的数据转为领域模型即可
嵌套领域对象,单个查询
但是当出现下面这样的模型时
//示例
public interface Sample extends DomainEntity {
String getSample();
User getUser();
}
领域模型中还持有了其他的领域模型
很明显是和其他的表产生了关联
而我们的t_sample
表可能是这样的结构
id | sample | user_id |
---|---|---|
sample1 | sample1 | user1 |
sample2 | sample2 | user2 |
这个时候可以这样实现用户存储
//基于 MBP 的示例存储实现
@Repository
public class MBPSampleRepository extends MBPDomainRepository implements SampleRepository {
@Autowired
private SampleMapper sampleMapper;
@Autowired
private DomainFactory factory;
@Autowired
private DomainValidator validator;
@Override
public SamplePO do2po(Sample sample) {
SamplePO po = new SamplePO();
po.setId(sample.getId());
po.setUserId(sample.getUser().getId());
return po;
}
@Override
public Sample po2do(SamplePO po) {
//根据 userId 生成一个 User,该方法不会立即查询数据库,而会等到调用 User 的方法获取数据时才会触发查询
User user = factory.createObject(User.class, po.getUserId());
return new SampleImpl.Builder()
.id(po.getId())
.sample(po.getSample())
.user(user)
.users(users)
.build(validator);
}
@Override
public BaseMapper getBaseMapper() {
return sampleMapper;
}
}
通过DomainFactory
生成一个User
对象(实际上就是使用了动态代理暂时占了个位置)
只有当调用用户数据的时候才会触发查询(通过传入的id
查询数据)
嵌套领域对象,批量查询
查询列表的时候就又出现了一个问题
我们之前的AbstractDomainRepository
是这样实现的
//领域存储抽象类
//P 为数据模型
public abstract class AbstractDomainRepository implements DomainRepository {
//数据模型转领域模型
public abstract T po2do(P po);
//数据模型转领域模型
public Collection pos2dos(Collection