背景
本文也只是本人对DDD和Cola 4.0架构的浅显理解,在这个理解基础上将一个简单的用户权限体系整改为符合领域驱动设计的一个项目结构,有不对的地方请广大读者多多包容并指正。
下图是cola4.0的架构,我的实现更倾向这种架构。
业务场景分析
用户权限体系属于一个比较简单的系统,主要分为 用户、角色、菜单、机构、鉴权等这几个主要功能模块,其中还涉及用户-角色、角色-菜单、用户-机构等关系,此时就要考虑如何划分领域,一开始,我是打算直接一个用户领域搞定,主要是因为用户好像和其他几个模块都有交集,但是仔细思考,其实可以分的更细,让各业务的边界更加清晰,看下图。
各子域情况
1. 机构子域 :只处理机构业务的逻辑,不和其他子域产生耦合,在应用层处理组合逻辑
2. 用户子域 :只处理用户业务的逻辑,不和其他子域产生耦合,在应用层处理组合逻辑
3. 角色子域:只处理角色业务的逻辑,不和其他子域产生耦合,在应用层处理组合逻辑
4. 菜单子域:只处理菜单业务的逻辑,不和其他子域产生耦合,在应用层处理组合逻辑
5. 用户-机构子域 : 这个子域就会在其聚合根中维护用户值对象和机构值对象,相当于关联关系只存在于这里,具体看下面代码,会发现,在用户-机构子域中的repository中会引用用户和机构的repository ,虽然领域之间最好不要耦合,但是基于这种情况,也没办法,不过repository 是接口,已经在一定程度上降低了领域之间的耦合。
@Data
public class UserOrgDO {
private Long id;
private Long orgId;
private Long userId;
private OrgItem orgItem;
private UserItem userItem;
/**
* 判断是否被使用
*
* @return true: 被使用,false : 没被使用
*/
public boolean checkIsUsed() {
return userItem != null;
}
}
@Component
@RequiredArgsConstructor
public class UserOrgRepositoryImpl implements UserOrgRepository {
private final SysUserOrgMapper sysUserOrgMapper;
private final UserRepository userRepository;
private final OrgRepository orgRepository;
@Override
public List<UserOrgDO> queryByOrgIds(Long[] ids) {
CommonExceptionCode.PARAMS_ERROR
.assertNotNull(ids, "机构id不能为null")
.assertFalse(ids.length == 0, "机构id不能为空");
List<SysUserOrg> userOrgs = sysUserOrgMapper.selectList(Wrappers.<SysUserOrg>lambdaQuery().in(SysUserOrg::getOrgId, ids));
return convertToDO(userOrgs);
}
@Override
public List<UserOrgDO> queryByUserIds(Long[] ids) {
CommonExceptionCode.PARAMS_ERROR
.assertNotNull(ids, "机构id不能为null")
.assertFalse(ids.length == 0, "机构id不能为空");
List<SysUserOrg> userOrgs = sysUserOrgMapper.selectList(Wrappers.<SysUserOrg>lambdaQuery().in(SysUserOrg::getUserId, ids));
return convertToDO(userOrgs);
}
private List<UserOrgDO> convertToDO(List<SysUserOrg> userOrgs) {
return userOrgs.stream().map(x -> {
UserOrgDO userOrgDO = new UserOrgDO();
userOrgDO.setOrgId(x.getOrgId());
userOrgDO.setUserId(x.getUserId());
Optional<UserItem> userItemOptional = userRepository.queryById(x.getUserId());
userItemOptional.ifPresent(userOrgDO::setUserItem);
Optional<OrgItem> orgItemOptional = orgRepository.queryById(x.getOrgId());
orgItemOptional.ifPresent(userOrgDO::setOrgItem);
return userOrgDO;
}).collect(Collectors.toList());
}
}
6. 用户-角色子域:同上
7. 角色-菜单子域: 同上
项目结构
- adapter -- controller # controller的web接口 UserController MenuController RoleController OrgController -- app -- dto # 数据传输对象 -- converter # 实现dto和do的转换 -- service # 应用层,负责领域服务的组合 UserService MenuService RoleService OrgService -- domain -- org # 组织机构子域 -- exception # 业务异常 -- model -- repository -- impl OrgRepositoryImpl OrgRepository -- service OrgDomainService -- menu # 菜单子域 -- model -- repository -- impl MenuRepositoryImpl MenuRepository -- service MenuDomainService -- role # 角色子域 -- model -- repository -- impl RoleRepositoryImpl RoleRepository -- service RoleDomainService -- user # 用户子域 -- model -- enums -- repository -- impl UserRepositoryImpl UserRepository -- service UserDomainService -- userOrg # 用户和组织机构的关系子域 -- model UserOrgDO -- repository -- impl UserOrgRepositoryImpl UserOrgRepository -- service UserOrgDomainService -- roleMenu # 角色和菜单的关系子域 -- model RoleMenuDO -- repository -- impl RoleMenuRepositoryImpl RoleMenuRepository -- service RoleMenuDomainService -- userRole # 用户和角色的关系子域 -- model UserRoleDO -- repository -- impl UserRoleRepositoryImpl UserRoleRepository -- service UserRoleDomainService -- infrastructure -- config # 配置文件 -- mapper # 数据库操作(持久化映射) -- entity # 数据库实体(与领域实体映射) -- utils # 工具类 -- exception # 异常处理 -- vo # 请求和响应对象 -- constant # 常量 -- filter # 过滤器
项目代码
假如我写的没问题的话,我再贴代码地址吧。
心得
1. 域之间最好是不要产生依赖,若是非要产生,请用接口 或者 抽象类
2. 域中不能使用DTO,只能用DO
3. 基础设施层可以作为领域层对外访问的中介,领域层提供接口,防腐层实现去处理外部接口
4. 业务域可以使用通用域
5. 业务域中没有领域实体也可以
6. 应用层可以调用 基础设施层、领域层
7. 领域实体拥有唯一标识,聚合的概念是为了保证数据的一致性,充血模型,包含一些方法
8. 值对象只有构造方法和getter方法,往往是一组不可拆分的属性
9. 聚合就是业务相关的属性和值对象的组合,入口就是聚合根
10. VO在api层转化为DTO,然后传递给应用层,应用层将DTO转化成为DO,传递给领域层,领域层将DO转化为PO,进行数据库操作
11. 枚举类的位置,取决于是通用的还是指定业务的,指定业务的,就放在业务领域中,通用的就放在通用域中