DDD 学习第一章

一、Domain Primitive(Domain)

让我们重新来定义一下 Domain Primitive :Domain Primitive 是一个在特定领域里,拥有精准定义的、可自我验证的、拥有行为的 Value Object 。

  • DP是一个传统意义上的Value Object,拥有Immutable的特性
  • DP是一个完整的概念整体,拥有精准定义
  • DP使用业务域中的原生语言
  • DP可以是业务域的最小组成部分、也可以构建复杂组合

注:Domain Primitive的概念和命名来自于Dan Bergh Johnsson & Daniel Deogun的书 Secure by Design。

使用 Domain Primitive 的三原则

  • 让隐性的概念显性化
  • 让隐性的上下文显性化
  • 封装多对象行为

Domain Primitive 和 DDD 里 Value Object 的区别

在 DDD 中, Value Object 这个概念其实已经存在:

  • 在 Evans 的 DDD 蓝皮书中,Value Object 更多的是一个非 Entity 的值对象
  • 在Vernon的IDDD红皮书中,作者更多的关注了Value Object的Immutability、Equals方法、Factory方法等

Domain Primitive 是 Value Object 的进阶版,在原始 VO 的基础上要求每个 DP 拥有概念的整体,而不仅仅是值对象。在 VO 的 Immutable 基础上增加了 Validity 和行为。当然同样的要求无副作用(side-effect free)。

Domain Primitive 和 Data Transfer Object (DTO) 的区别

在日常开发中经常会碰到的另一个数据结构是 DTO ,比如方法的入参和出参。DP 和 DTO 的区别如下:

什么情况下应该用 Domain Primitive

常见的 DP 的使用场景包括:

  • 有格式限制的 String:比如Name,PhoneNumber,OrderNumber,ZipCode,Address等
  • 有限制的Integer:比如OrderId(>0),Percentage(0-100%),Quantity(>=0)等
  • 可枚举的 int :比如 Status(一般不用Enum因为反序列化问题)
  • Double 或 BigDecimal:一般用到的 Double 或 BigDecimal 都是有业务含义的,比如 Temperature、Money、Amount、ExchangeRate、Rating 等
  • 复杂的数据结构:比如 Map<String, List<Integer>> 等,尽量能把 Map 的所有操作包装掉,仅暴露必要行为

二、Data Object(DO)

DO是单纯的和数据库表的映射关系,每个字段对应数据库表的一个column,这种对象叫Data Object。DO只有数据,没有行为。DO的作用是对数据库做快速映射,避免直接在代码里写SQL。无论你用的是MyBatis还是Hibernate这种ORM,从数据库来的都应该先直接映射到DO上,但是代码里应该完全避免直接操作 DO。

三、Entity

Entity 是基于领域逻辑的实体类,它的字段和数据库储存不需要有必然的联系。Entity包含数据,同时也应该包含行为在 Entity 里,字段也不仅仅是String等基础类型,而应该尽可能用上一讲的 Domain Primitive 代替,可以避免大量的校验代码

四、DAO和Repository

  • DAO对应的是一个特定的数据库类型的操作,相当于SQL的封装。所有操作的对象都是DO类,所有接口都可以根据数据库实现的不同而改变。比如,insert 和 update 属于数据库专属的操作。

  • Repository对应的是Entity对象读取储存的抽象,在接口层面做统一,不关注底层实现。比如,通过 save 保存一个Entity对象,但至于具体是 insert 还是 update 并不关心。Repository的具体实现类通过调用DAO来实现各种操作,通过Builder/Factory对象实现AccountDO 到 Account之间的转化

五、Repository和Entity

  • 通过Entity对象,避免了其他业务逻辑代码和数据库的直接耦合,避免了当数据库字段变化时,大量业务逻辑也跟着变的问题。

  • 通过Repository,改变业务代码的思维方式,让业务逻辑不再面向数据库编程,而是面向领域模型编程

  • Entity属于一个完整的内存中对象,可以比较容易的做完整的测试覆盖,包含其行为。

  • Repository作为一个接口类,可以比较容易的实现Mock或Stub,可以很容易测试。

  • RepositoryImpl实现类,由于其职责被单一出来,只需要关注Entity到DO的映射关系和Repository方法到DAO方法之间的映射关系,相对于来说更容易测试。

六、防腐层

这种常见的设计模式叫做Anti-Corruption Layer(防腐层或ACL)。很多时候我们的系统会去依赖其他的系统,而被依赖的系统可能包含不合理的数据结构、API、协议或技术实现,如果对外部系统强依赖,会导致我们的系统被”腐蚀“。这个时候,通过在系统间加入一个防腐层,能够有效的隔离外部依赖和内部逻辑,无论外部如何变更,内部代码可以尽可能的保持不变。

ACL 不仅仅只是多了一层调用,在实际开发中ACL能够提供更多强大的功能:

  • 适配器:很多时候外部依赖的数据、接口和协议并不符合内部规范,通过适配器模式,可以将数据转化逻辑封装到ACL内部,降低对业务代码的侵入。在这个案例里,我们通过封装了ExchangeRate和Currency对象,转化了对方的入参和出参,让入参出参更符合我们的标准。
  • 缓存:对于频繁调用且数据变更不频繁的外部依赖,通过在ACL里嵌入缓存逻辑,能够有效的降低对于外部依赖的请求压力。同时,很多时候缓存逻辑是写在业务代码里的,通过将缓存逻辑嵌入ACL,能够降低业务代码的复杂度。
  • 兜底:如果外部依赖的稳定性较差,一个能够有效提升我们系统稳定性的策略是通过ACL起到兜底的作用,比如当外部依赖出问题后,返回最近一次成功的缓存或业务兜底数据。这种兜底逻辑一般都比较复杂,如果散落在核心业务代码中会很难维护,通过集中在ACL中,更加容易被测试和修改。
  • 易于测试:类似于之前的Repository,ACL的接口类能够很容易的实现Mock或Stub,以便于单元测试。
  • 功能开关:有些时候我们希望能在某些场景下开放或关闭某个接口的功能,或者让某个接口返回一个特定的值,我们可以在ACL配置功能开关来实现,而不会对真实业务代码造成影响。同时,使用功能开关也能让我们容易的实现Monkey测试,而不需要真正物理性的关闭外部依赖。

七、抽象中间件

对各种中间件的抽象的目的是让业务代码不再依赖中间件的实现逻辑。因为中间件通常需要有通用型,中间件的接口通常是String或Byte[] 类型的,导致序列化/反序列化逻辑通常和业务逻辑混杂在一起,造成胶水代码。通过中间件的ACL抽象,减少重复胶水代码。

八、Domain Service

用于封装Domain Primitive的多对象逻辑。

九、DDD

如果今天能够重新写这段代码,考虑到最终的依赖关系,我们可能先写Domain层的业务逻辑,然后再写Application层的组件编排,最后才写每个外部依赖的具体实现。这种架构思路和代码组织结构就叫做Domain-Driven Design(领域驱动设计,或DDD)。所以DDD不是一个特殊的架构设计,而是所有Transction Script代码经过合理重构后一定会抵达的终点。

9.1 代码的演进/变化速度

在传统架构中,代码从上到下的变化速度基本上是一致的,改个需求需要从接口、到业务逻辑、到数据库全量变更,而第三方变更可能会导致整个代码的重写。但是在DDD中不同模块的代码的演进速度是不一样的:

  • Domain层属于核心业务逻辑,属于经常被修改的地方。比如:原来不需要扣手续费,现在需要了之类的。通过Entity能够解决基于单个对象的逻辑变更,通过Domain Service解决多个对象间的业务逻辑变更。
  • Application层属于Use Case(业务用例)。业务用例一般都是描述比较大方向的需求,接口相对稳定,特别是对外的接口一般不会频繁变更。添加业务用例可以通过新增Application Service或者新增接口实现功能的扩展。
  • Infrastructure层属于最低频变更的。一般这个层的模块只有在外部依赖变更了之后才会跟着升级,而外部依赖的变更频率一般远低于业务逻辑的变更频率。

所以在DDD架构中,能明显看出越外层的代码越稳定,越内层的代码演进越快,真正体现了领域“驱动”的核心思想。

DDD不是一个什么特殊的架构,而是任何传统代码经过合理的重构之后最终一定会抵达的终点。DDD的架构能够有效的解决传统架构中的问题:

  • 高可维护性:当外部依赖变更时,内部代码只用变更跟外部对接的模块,其他业务逻辑不变。
  • 高可扩展性:做新功能时,绝大部分的代码都能复用,仅需要增加核心业务逻辑即可。
  • 高可测试性:每个拆分出来的模块都符合单一性原则,绝大部分不依赖框架,可以快速的单元测试,做到100%覆盖。
  • 代码结构清晰:通过POM module可以解决模块间的依赖关系, 所有外接模块都可以单独独立成Jar包被复用。当团队形成规范后,可以快速的定位到相关代码。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值