写点什么

设计模式之美 -- 充血模型的 DDD 开发模式例子

作者:GalaxyCreater
  • 2022-11-30
    广东
  • 本文字数:2410 字

    阅读完需:约 8 分钟

DDD is nothing more than OOP applied to business models.

DDD 其实就是把 OOP 应用于业务模型。


其他层和贫血模型都一样,service 层实现:

public class VirtualWallet { // Domain领域模型(充血模型);说白了就是把逻辑提取别的类实现                             // (前提是这个类满足领域)  private Long id;  private Long createTime = System.currentTimeMillis();;  private BigDecimal balance = BigDecimal.ZERO;    public VirtualWallet(Long preAllocatedId) {    this.id = preAllocatedId;  }    public BigDecimal balance() {    return this.balance;  }    public void debit(BigDecimal amount) {    if (this.balance.compareTo(amount) < 0) {      throw new InsufficientBalanceException(...);    }    this.balance = this.balance.subtract(amount);  }    public void credit(BigDecimal amount) {    if (amount.compareTo(BigDecimal.ZERO) < 0) {      throw new InvalidAmountException(...);    }    this.balance = this.balance.add(amount);  }}
public class VirtualWalletService { // 通过构造函数或者IOC框架注入 private VirtualWalletRepository walletRepo; private VirtualWalletTransactionRepository transactionRepo; public VirtualWallet getVirtualWallet(Long walletId) { VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId); VirtualWallet wallet = convert(walletEntity); return wallet; } public BigDecimal getBalance(Long walletId) { return walletRepo.getBalance(walletId); } @Transactional public void debit(Long walletId, BigDecimal amount) { VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId); VirtualWallet wallet = convert(walletEntity); wallet.debit(amount); VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity(); transactionEntity.setAmount(amount); transactionEntity.setCreateTime(System.currentTimeMillis()); transactionEntity.setType(TransactionType.DEBIT); transactionEntity.setFromWalletId(walletId); transactionRepo.saveTransaction(transactionEntity); walletRepo.updateBalance(walletId, wallet.balance()); } @Transactional public void credit(Long walletId, BigDecimal amount) { // 省略 }
@Transactional public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) { //...跟基于贫血模型的传统开发模式的代码一样... }}
复制代码


问题 1:在基于充血模型的 DDD 开发模式中,将业务逻辑移动到 Domain 中,Service 类变得很薄,但在我们的代码设计与实现中,并没有完全将 Service 类去掉,这是为什么?或者说,Service 类在这种情况下担当的职责是什么?哪些功能逻辑会放到 Service 类中?

目的:

Service 类负责一些不适合放在 Domain 类中的功能。比如,负责与 Repository 层打交道、跨领域模型的业务聚合功能、幂等事务等非功能性的工作。

  • Service 类负责与 Repository 交流。之所以让 VirtualWalletService 类与 Repository 打交道,而不是让领域模型 VirtualWallet 与 Repository 打交道,那是因为我们想保持领域模型的独立性,不与任何其他层的代码(Repository 层的代码)或开发框架(比如 Spring、MyBatis)耦合在一起,将流程性的代码逻辑(比如从 DB 中取数据、映射数据)与领域模型的业务逻辑解耦,让领域模型更加可复用

  • Service 类负责跨领域模型的业务聚合功能。VirtualWalletService 类中的 transfer() 转账函数会涉及两个钱包的操作,因此这部分业务逻辑无法放到 VirtualWallet 类中,所以,我们暂且把转账业务放到 VirtualWalletService 类中了。当然,虽然功能演进,使得转账业务变得复杂起来之后,我们也可以将转账业务抽取出来,设计成一个独立的领域模型。


问题 2:在基于充血模型的 DDD 开发模式中,尽管 Service 层被改造成了充血模型,但是 Controller 层和 Repository 层还是贫血模型,是否有必要也进行充血领域建模呢?

没必要。

  • 包含的业务逻辑并不多,必要做充血建模,否则过度设计。

  • Repository 层的 Entity:虽然贫血模型有容易被到处修改的风险,但是因为生命周期短,所以不存在这种情况。

  • Controller 层的 VO(view object)。实际上 VO 是一种 DTO(Data Transfer Object,数据传输对象)。从功能上来讲,它理应不包含业务逻辑、只包含数据。


如何理解 DDD:

为了降低软件复杂度,让软件容易维护。

理解 OOP,我们就不难理解 DDD:

  • DDD 第一原则:将数据和操作结合。(贫血模型将数据和操作分离,违反 OOP 的原则。)(封装)

  • DDD 第二原则:界限上下文。这是将“单一职责”应用于我们的领域模型。 (还是封装)


实现方法:

  1. 使用通用语言(Ubiquitous Language):类、方法、字段的命名,要符合业务。使用业务语言命名,以后在和客户或者其他团队交流时能够更顺畅。

  2. 理解系统业务


DDD 中的 Service 层

1. Service 层只是一个中间层,起到连接和组合作用。 用于支持领域模型层和 Repository 层的交互(连接作用),利用各种领域对象执行业务逻辑(组合作用)。 比如通过 Repository 查出数据,将数据转换为领域模型对象,利用领域模型对象执行业务逻辑(核心),然后调用 Repository 更新领域模型中的数据。

2. Service 类还负责一些非功能性及与三方系统交互的工作。 比如幂等、事务、发邮件、发消息、记录日志、调用其他系统的 RPC 接口等。

不允许 Service 中的逻辑过于复杂,如果 Service 中的组合的业务逻辑过于复杂,我们就要将这业务逻辑抽取出一个新的领域对象进行封装,通过调用这个领域对象来进行这些复杂的操作。

发布于: 刚刚阅读数: 3
用户头像

GalaxyCreater

关注

还未添加个人签名 2019-04-21 加入

还未添加个人简介

评论

发布
暂无评论
设计模式之美--充血模型的DDD开发模式例子_设计模式_GalaxyCreater_InfoQ写作社区