DDD理解

一、Repository设计

  1. 接口名称不应该使用底层实现的语法:我们常见的insert、select、update、delete都属于SQL语法,使用这几个词相当于和DB底层实现做了绑定。相反,我们应该把 Repository 当成一个中性的类 似Collection 的接口,使用语法如 find、save、remove。在这里特别需要指出的是区分 insert/add 和 update 本身也是一种和底层强绑定的逻辑,一些储存如缓存实际上不存在insert和update的差异,在这个 case 里,使用中性的 save 接口,然后在具体实现上根据情况调用 DAO 的 insert 或 update 接口

  2. 出参入参不应该使用底层数据格式:需要记得的是 Repository 操作的是  Entity 对象(实际上应该是Aggregate Root),而不应该直接操作底层的 DO 。更近一步,Repository 接口实际上应该存在于Domain层,但实现类在Infrastructure,根本看不到 DO 的实现。这个也是为了避免底层实现逻辑渗透到业务代码中的强保障。

  3. 应该避免所谓的“通用”Repository模式:很多 ORM 框架都提供一个“通用”的Repository接口,然后框架通过注解自动实现接口,比较典型的例子是Spring Data、Entity Framework等,这种框架的好处是在简单场景下很容易通过配置实现,但是坏处是基本上无扩展的可能性(比如加定制缓存逻辑),在未来有可能还是会被推翻重做。当然,这里避免通用不代表不能有基础接口和通用的帮助类,具体如下。

  4. 一个聚合一个repository。

三、避免写流水式代码

对代码做如下步骤的拆分

1,抽象接口层,统一对外的服务,rpc的话直接@dubbo

2,

接口层主要由以下几个功能组成:

  1. 网络协议的转化:通常这个已经由各种框架给封装掉了,我们需要构建的类要么是被注解的bean,要么是继承了某个接口的bean。
  2. 统一鉴权:比如在一些需要AppKey+Secret的场景,需要针对某个租户做鉴权的,包括一些加密串的校验
  3. Session管理:一般在面向用户的接口或者有登陆态的,通过Session或者RPC上下文可以拿到当前调用的用户,以便传递给下游服务。
  4. 限流配置:对接口做限流避免大流量打到下游服务
  5. 前置缓存:针对变更不是很频繁的只读场景,可以前置结果缓存到接口层
  6. 异常处理:通常在接口层要避免将异常直接暴露给调用端,所以需要在接口层做统一的异常捕获,转化为调用端可以理解的数据格式
  7. 日志:在接口层打调用日志,用来做统计和debug等。一般微服务框架可能都直接包含了这些功能。

当然,如果有一个独立的网关设施/应用,则可以抽离出鉴权、Session、限流、日志等逻辑,但是目前来看API网关也只能解决一部分的功能,即使在有API网关的场景下,应用里独立的接口层还是有必要的。

在interface层,鉴权、Session、限流、缓存、日志等都比较直接,只有一个异常处理的点需要重点说下。

规范:Interface层的HTTP和RPC接口,返回值为Result,捕捉所有异常,可以使用aop或者用@ExceptionHandler @ControllerAdvice写统一异常处理类

规范:Application层的所有接口返回值为DTO,不负责处理异常

规范:一个Interface层的类应该是“小而美”的,应该是面向“一个单一的业务”或“一类同样需求的业务”,需要尽量避免用同一个类承接不同类型业务的需求。遵循单一职责原则

Application层的几个核心类:

  1. ApplicationService应用服务:最核心的类,负责业务流程的编排,但本身不负责任何业务逻辑
  2. DTO Assembler:负责将内部领域模型转化为可对外的DTO
  3. Command(写)、Query(读)、Event(写 返回void)对象:作为ApplicationService的入参
  4. 返回的DTO:作为ApplicationService的出参

规范:ApplicationService的接口入参只能是一个Command、Query或Event对象,CQE对象需要能代表当前方法的语意。

规范:唯一可以的例外是根据单一ID查询的情况,可以省略掉一个Query对象的创建。

规范:CQE对象的校验应该前置,避免在ApplicationService里做参数的校验。可以通过JSR303/380和Spring Validation来实现。validateBean

规范:由于CQE是有语意的,所以针对不同的语意指令,要避免CQE对象的复用。如update和create语意是不一样的,所以要写两个command。

规范:ApplicationService只负责了业务流程的编排,不负责业务逻辑处理,是将原有业务流水账代码剥离了校验逻辑、领域计算、持久化等逻辑之后剩余的流程,是“胶水层”代码。(AppService通常不做任何决策(Precondition除外),仅仅是把所有决策交给DomainService或Entity,把跟外部交互的交给Infrastructure接口,如Repository或防腐层)

规范:ApplicationService应该永远返回DTO而不是Entity。入参是CQE,返回DTO,所以需要将一个或多个Entity/VO,转化为DTO的DTO Assembler,(注意:DTO Assembler通常不建议有反操作,也就是不会从DTO到Entity,因为通常一个DTO转化为Entity时是无法保证Entity的准确性的。)推荐MapStruct。

规范:Application层只返回DTO,可以直接抛异常,不用统一处理。所有调用到的服务也都可以直接抛异常,除非需要特殊处理,否则不需要刻意捕捉异常,同理Domain层,以及Infrastructure层,遇到错误直接抛异常是最合理的方法。

规范:Application层是“用例”而不是“接口”,是相对稳定的存在。

怎么判断ApplicationService是否有业务逻辑

1,不要有if/else分支逻辑:也就是说代码的Cyclomatic Complexity(循环复杂度)应该尽量等于1(中断判断除外)

2,不要有任何计算

3,一些数据的转化可以交给其他对象来做:比如DTO Assembler,将对象间转化的逻辑沉淀在单独的类中,降低ApplicationService的复杂度

ApplicationService的代码通常有类似的结构:

AppService通常不做任何决策(Precondition除外),仅仅是把所有决策交给DomainService或Entity,把跟外部交互的交给Infrastructure接口,如Repository或防腐层。

一般的“套路”如下:

  • 准备数据:包括从外部服务或持久化源取出相对应的Entity、VO以及外部服务返回的DTO。
  • 执行操作:包括新对象的创建、赋值,以及调用领域对象的方法对其进行操作。需要注意的是这个时候通常都是纯内存操作,非持久化。
  • 持久化:将操作结果持久化,或操作外部系统产生相应的影响,包括发消息等异步操作。

规范:防腐层service类名:xxxFacade或者xxxProxy

规范:Entity的规则是不能直接变更其属性,必须通过Entity的方法去对内部状态做变更。这样能保证数据的一致性。

领域对象组件化,通过实体实现接口(接口里定义行为),达到组件化的目的。

在DDD里,一个Entity不应该直接参考另一个Entity或服务,也就是说以下的代码是错误的:

public class Player {    @Autowired    EquipmentService equipmentService; // BAD: 不可以直接依赖
    public void equip(Weapon weapon) {       // ...    }}

 正确的引入方式应该是:

public class Player {    public void equip(Weapon weapon, EquipmentService equipmentService) {        if (equipmentService.canEquip(this, weapon)) {            this.weaponId = weapon.getId();        } else {            throw new IllegalArgumentException("Cannot Equip: " + weapon);        }    }}

注意:重载是在编译时就决定了,所以无法在运行时动态决定。重写才是动态运行时决定的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值