手把手教你落地 DDD
钟敬
Thoughtworks 首席咨询师、数字化转型与运营团队 DDD 负责人
19697 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 45 讲
AIGC特别策划 (2讲)
结束语&结课测试 (2讲)
手把手教你落地 DDD
15
15
1.0x
00:00/00:00
登录|注册

11|代码实现(中):怎样创建领域对象、实现领域逻辑?

你好,我是钟敬。今天咱们继续撸代码。
上节课我们解决了层间依赖的问题,今天我们讨论几个更深入的问题。
第一,在面向过程的程序里,领域逻辑一般是写在应用服务里的,那么,DDD 有什么不同的思路呢?为了解决这个问题,我们需要掌握 DDD 的领域服务模式和表意接口模式。
第二,过去我们常常在应用服务里面直接 “New” 出领域对象,如果创建领域对象的逻辑比较复杂,那要怎么办呢?对于这个问题,我们需要了解 DDD 的工厂模式。
另外,尽管我们已经介绍了分层架构和模块模式,但实现的时候,你可能还会有一些困惑,我们也会在这节课里一并解决。后面代码比较多,建议你一边看文稿,一边听我说。

“表意接口”(Intention-Revealing Interfaces)模式

“添加组织”这个功能的领域逻辑主要体现在各种校验规则上,咱们先粗略地看看应用服务的代码的结构,暂时不需要细看每个校验的具体逻辑。
package chapter11.unjuanable.application.orgmng;
// imports...
@Service
public class OrgService {
private final UserRepository userRepository;
private final TenantRepository tenantRepository;
private final OrgTypeRepositoryJdbc orgTypeRepository;
private final OrgRepository orgRepository;
private final EmpRepository empRepository;
@Autowired
public OrgService(UserRepository userRepository
, TenantRepository tenantRepository
, OrgRepository orgRepository
, EmpRepository empRepository
, OrgTypeRepositoryJdbc orgTypeRepository) {
//为注入的 Repository 赋值...
}
// "添加组织"功能的入口
public OrgDto addOrg(OrgDto request, Long userId) {
validate(request);
Org org = buildOrg(request, userId);
org = orgRepository.save(org);
return buildOrgDto(org);
}
private OrgDto buildOrgDto(Org org) {
//将领域对象转成DTO...
}
private Org buildOrg(OrgDto request, Long userId) {
//将DTO转成领域对象...
}
//主要的领域逻辑在这个方法
private void validate(OrgDto request) {
final var tenant = request.getTenant();
// 租户必须有效
if (!tenantRepository.existsByIdAndStatus(tenant, TenantStatus.EFFECTIVE)) {
throw new BusinessException("id为'" + tenant
+ "'的租户不是有效租户!");
}
// 组织类别不能为空
if (isBlank(request.getOrgType())) {
throw new BusinessException("组织类别不能为空!");
}
// 企业是在创建租户的时候创建好的,因此不能单独创建企业
if ("ENTP".equals(request.getOrgType())) {
throw new BusinessException("企业是在创建租户的时候创建好的,因此不能单独创建企业!");
}
// 组织类别必须有效
if (!orgTypeRepository.existsByCodeAndStatus(tenant, request.getOrgType(), OrgTypeStatus.EFFECTIVE)) {
throw new BusinessException("'" + request.getOrgType()
+ "'不是有效的组织类别代码!");
}
// 上级组织应该是有效组织
Org superior = orgRepository.findByIdAndStatus(tenant
, request.getSuperior(), OrgStatus.EFFECTIVE)
.orElseThrow(() ->
new BusinessException("'" + request.getSuperior()
+ "' 不是有效的组织 id !"));
// 取上级组织的组织类别
OrgType superiorOrgType = orgTypeRepository.findByCodeAndStatus(tenant
, superior.getOrgType()
, OrgTypeStatus.EFFECTIVE)
.orElseThrow(() ->
new DirtyDataException("id 为 '"
+ request.getSuperior()
+ "' 的组织的组织类型代码 '"
+ superior.getOrgType() + "' 无效!"));
// 开发组的上级只能是开发中心
if ("DEVGRP".equals(request.getOrgType()) && !"DEVCENT".equals(superiorOrgType.getCode())) {
throw new BusinessException("开发组的上级(id = '"
+ request.getSuperior() + "')不是开发中心!");
}
// 开发中心和直属部门的上级只能是企业
if (("DEVCENT".equals(request.getOrgType()) || "DIRDEP".equals(request.getOrgType()))
&& !"ENTP".equals(superiorOrgType.getCode())) {
throw new BusinessException("开发中心或直属部门的上级(id = '"
+ request.getSuperior() + "')不是企业!");
}
// 组织负责人可以空缺,如果有的话,的必须是一个在职员工(含试用期)
if (request.getLeader() != null
&& !empRepository.existsByIdAndStatus(tenant, request.getLeader()
, EmpStatus.REGULAR, EmpStatus.PROBATION)) {
throw new BusinessException("组织负责人(id='"
+ request.getLeader() + "')不是在职员工!");
}
// 组织必须有名称
if (isBlank(request.getName())) {
throw new BusinessException("组织没有名称!");
}
// 同一个组织下的下级组织不能重名
if (orgRepository.existsBySuperiorAndName(tenant, request.getSuperior(), request.getName())) {
throw new BusinessException("同一上级下已经有名为'"
+ request.getName() + "'的组织存在!");
}
}
}
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了领域驱动设计(DDD)中的实践技巧,重点讨论了领域逻辑的实现和领域对象的创建,以及程序模块的划分问题。作者介绍了领域服务模式和表意接口模式在解决面向过程程序中领域逻辑写在应用服务中的问题时的重要性。此外,文章还讨论了在应用服务中直接创建领域对象的问题以及如何应对创建领域对象的复杂逻辑,需要了解DDD的工厂模式。文章还介绍了“表意接口”模式,通过抽取函数的重构手法,将每个规则都抽成独立的方法,并按含义进行命名,体现了领域驱动设计的模式。总体来看,本文将深入讨论领域对象的创建和领域逻辑的实现,涉及到DDD的相关模式和架构实现细节。文章内容深入浅出,适合读者快速了解领域驱动设计中的实践技巧。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《手把手教你落地 DDD》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(30)

  • 最新
  • 精选
  • 赵晏龙
    1你问我值不值?我当然说值!代码即文档 2表意接口的接口只是个概念,不是编程语言中的interface。

    作者回复: 全中👍🏻

    2023-01-02归属地:广东
    16
  • Michael
    问题1.值得 从代码维护的角度说,抽方法可以用方法名来表意 从代码设计上说,方法也是一种抽象,应该依赖抽象而不是实现 问题2.这里的接口指的是抽象,不是编程语言里的语言特性,应该跟语言无关 关于隐喻,想请教老师,到底什么是隐喻呢?看徐老师的业务建模,他也提到为系统找到一个简洁的隐喻来解决问题之类的,不是很懂,老师可否再解释解释

    作者回复: 两个问题都回答得很到位 👍🏻 隐喻实际上就是初中学修辞时学的“暗喻”或“打比方”,以便容易理解概念。比如说把多台计算机连起来,这个概念不好理解,但“网”这个词自古就有,那么说成计算机网络就好理解了。计算机连起来,和原来的渔网,蜘蛛网等本来没关系,这里是用“网”来打比方。

    2022-12-29归属地:广东
    7
  • 虚竹
    builder模式用起来还是过于繁琐,因为实际业务参数可能很多,我们更可能选择将DTO对象定义下沉,通过内层包或者类名后缀知道它是用于应用层的

    作者回复: 这样也是一种可行的选择。下节课还会有另一种方法,稍微牺牲一些封装性

    2022-12-29归属地:广东
    3
    4
  • 瀚海
    感觉为了DTO类不破坏层间依赖关系,而引入builder、factory实在过于重了

    作者回复: factory主要不是为了层间依赖,而是为了把复杂的创建逻辑抽出来。课程里也给了另一种方法:assembler.

    2023-07-19归属地:上海
    3
  • Ice
    DTO是否可以放到common中作为支撑层被各层引用?

    作者回复: 也是一种可行的思路,不过这样可能导致DTO为了满足各个层面的传输要求而变得复杂,要慎重一点。最好不要让领域层依赖这个意义上的DTO。

    2023-02-03归属地:四川
    2
  • Tree
    老师,课程中涉及到的代码,会开源出来放在github上吗?

    作者回复: 会,但没这么快

    2022-12-29归属地:广东
    3
    2
  • 6点无痛早起学习的和尚
    一些问题: 3. 这里的校验规则是否又分的太多类了,是否直接用一个类里封装校验规则即可 4. 为什么不把校验规则放到相应的 Dto 实体类里去,而去单独去抽离一个“领域服务”模式 5. 用复杂的 Builder 模式去创建领域对象,有点太复杂了,实际项目中,应该不会采用这种方式,为了完全依赖关系去牺牲代码编码性,这个实际上需要权衡。

    作者回复: 3.原则是一个类不要太大,要关注点分离,至于怎么分,可以斟酌,你可以看看有没有其他方案 4.首先,DTO和实体不一样,DTO只传数据,不应该有业务规则。至于是否放在实体,原则是优先考虑放在实体,不行的话再放领域服务。之所以我们现在放在领域服务,是因为我们约定了实体不访问数据库,而这些逻辑要访问数据库。如果约定实体也可以访问数据库,那么有些逻辑放在实体也可以 5.是的,要权衡。

    2023-01-16归属地:北京
    1
  • 老狗
    最可怕的是有注释但是注释说的跟代码不是一回事

    作者回复: 哈哈,说得对

    2023-01-10归属地:广东
    1
  • 张逃逃
    有个疑问请教老师,为什么不把Validator的逻辑封装到Repository?

    作者回复: 作为适配器的Repository应该单纯一点,就是数据访问,其他什么都不做。

    2023-01-08归属地:广东
    1
  • 过去,单纯的以为领域服务就是XxxDomainService,然后在这个类中封闭很多方法。看了老师的解释,恍然大悟。但是,在实践的过程中,有一些业务规则,是需要通过不同的上下文或者聚合交互验证实现的,所以这些规则很难抽象到独立的、具体的某一个领域服务中,于是,一些原本属于领域层的知识在应用层进行了组合,这使得应用层又特别的臃肿且破坏了分层也只能边界,这种情况,老师要如何解决?或者类似的解决方案?

    作者回复: 你实际上问了两个层面的问题,一个是同一个上下文中,跨聚合的领域逻辑;另一个是跨上下文的领域逻辑。 关于跨聚合的逻辑,直接在领域服务中实现就可以了。如果您觉得这样做有问题,请举一个具体的例子来谈。 关于跨上下文的领域逻辑,可以引入上下文映射和防腐层来解决。这个问题咱们在第三迭代,讲限界上下文的时候再谈。

    2022-12-30归属地:广东
    3
    1
收起评论
显示
设置
留言
30
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部