DDD的精髓(1)

简介: DDD的精髓(1)

你可以,不代表你应该。

(Just because you can, doesn’t mean you should.)

——施莉琳•凯尼恩


在第6章中,我们简要介绍了什么是模型、模型在软件开发中的重要性,以及一些常用的建模方式在软件工程中的应用。本章将重点讲解领域驱动设计(Domain Driven Design,DDD),包括DDD的重要概念,以及如何进行领域建模。


7.1 什么是DDD


DDD是Eric Evans在2003年出版的《领域驱动设计:软件核心复杂性应对之道》(Domain-Driven Design: Tackling Complexity in the Heart of Software)一书中提出的具有划时代意义的重要概念,是指通过统一语言、业务抽象、领域划分和领域建模等一系列手段来控制软件复杂度的方法论。


DDD的革命性在于领域驱动设计是面向对象分析的方法论,它可以利用面向对象的特性(封装、多态)有效地化解复杂性,而传统J2EE或Spring+Hibernate等事务性编程模型只关心数据。这些数据对象除了简单的setter/getter方法外,不包含任何业务逻辑,业务逻辑都是以过程式的代码写在Service中。这种方式极易上手,但随着业务的发展,系统也很容易变得混乱复杂。


7.2 初步体验DDD


在介绍DDD之前,我喜欢用这个银行转账的案例来做一个DDD和事务脚本(Transaction Script)的简单对比。我们要实现一个银行转账的功能,如果用传统的事务脚本方式实现,业务逻辑通常会被写在MoneyTransferService中,而Account仅仅是getters和setters的数据结构,也就是所谓的“贫血模式”。其代码如下所示:


public class MoneyTransferServiceTransactionScriptImpl
      implements MoneyTransferService {
  private AccountDao accountDao;
  private BankingTransactionRepository bankingTransactionRepository;
  . . .
  @Override
  public BankingTransaction transfer(
      String fromAccountId, String toAccountId, double amount) {
    Account fromAccount = accountDao.findById(fromAccountId);
    Account toAccount = accountDao.findById(toAccountId);
    . . .
    double newBalance = fromAccount.getBalance() - amount;
    switch (fromAccount.getOverdraftPolicy()) {
    case NEVER:
      if (newBalance < 0) {
        throw new DebitException("Insufficient funds");
      }
      break;
    case ALLOWED:
      if (newBalance < -limit) {
        throw new DebitException(
            "Overdraft limit (of " + limit +") exceeded: " + newBalance);
      }
      break;
    }
    fromAccount.setBalance(newBalance);
    toAccount.setBalance(toAccount.getBalance() + amount);
    BankingTransaction moneyTransferTransaction =
        new MoneyTranferTransaction(fromAccountId,toAccountId,amount);
    bankingTransactionRepository.addTransaction(moneyTransferTransaction);
    return moneyTransferTransaction;
  }}


上述代码有些读者可能会比较眼熟,因为大部分系统都是这么写的。评审完需求,工程师画几张UML图完成设计,就开始像上面这样写业务代码了,这样写基本不用太动脑筋,完全是过程式的代码风格。


同样的业务逻辑,接下来看使用领域建模是怎么做的。在使用DDD之后,Account实体除账号属性之外,还包含了行为和业务逻辑,比如debit()和credit()方法。


public class Account {
  private String id;
  private double balance;
  private OverdraftPolicy overdraftPolicy;
  . . .
  public double balance() { return balance; }
  public void debit(double amount) {
    this.overdraftPolicy.preDebit(this, amount);
    this.balance = this.balance - amount;
    this.overdraftPolicy.postDebit(this, amount);
  }
  public void credit(double amount) {
    this.balance = this.balance + amount;
  }}


透支策略OverdraftPolicy也不仅仅是一个Enum了,而是被抽象成包含业务规则并采用策略模式的对象。


public interface OverdraftPolicy {
  void preDebit(Account account, double amount);
  void postDebit(Account account, double amount);}public class NoOverdraftAllowed implements OverdraftPolicy {
  public void preDebit(Account account, double amount) {
    double newBalance = account.balance() - amount;
    if (newBalance < 0) {
      throw new DebitException("Insufficient funds");
    }
  }
  public void postDebit(Account account, double amount) {
  }}public class LimitedOverdraft implements OverdraftPolicy {
  private double limit;
  . . .
  public void preDebit(Account account, double amount) {
    double newBalance = account.balance() - amount;
    if (newBalance < -limit) {
      throw new DebitException(
          "Overdraft limit (of " + limit + ") exceeded: "+newBalance);
    }
  }
  public void postDebit(Account account, double amount) {
  }}
相关文章
|
设计模式 前端开发 关系型数据库
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例) 3
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例)
5569 2
|
缓存 前端开发 中间件
DDD 领域驱动设计落地实践系列:工程结构分层设计
前面几篇文章中,笔者给大家阐述了 DDD 领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行 DDD 落地实践进行了详细的讨论,但是还没有涉及到工程层面的落地。实际上所有的这些架构理论到最后都是为了使得我们代码结构更加清晰,从而开发出 bug 少、扩展性强、逻辑清楚的应用。因此本文就是为了解决 DDD 领域驱动落地实践最后一公里问题,将我们分析出来的领域模型通过与工程结构的映射实现真正的落地。
DDD 领域驱动设计落地实践系列:工程结构分层设计
|
前端开发 测试技术 API
DDD领域驱动设计实战-分层架构及代码目录结构(上)
DDD领域驱动设计实战-分层架构及代码目录结构
1872 0
DDD领域驱动设计实战-分层架构及代码目录结构(上)
|
设计模式 弹性计算 人工智能
阿里技术专家详解DDD系列 第四讲 - 领域层设计规范
在一个DDD架构设计中,领域层的设计合理性会直接影响整个架构的代码结构以及应用层、基础设施层的设计。但是领域层设计又是有挑战的任务,特别是在一个业务逻辑相对复杂应用中,每一个业务规则是应该放在Entity、ValueObject 还是 DomainService是值得用心思考的,既要避免未来的扩展性差,又要确保不会过度设计导致复杂性。
|
7月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
274 0
|
设计模式 架构师 Java
一文详谈领域驱动设计实践
本文作者结合在团队的实践过程,分享了自己对领域驱动设计的一些思考。
722 8
|
移动开发 小程序 JavaScript
uni-app多端应用开发:常见跨端兼容问题及处理策略
uni-app多端应用开发:常见跨端兼容问题及处理策略
1155 0
|
架构师
DDD建模系列(一)
DDD建模系列(一)
|
人工智能 自然语言处理 机器人
9411亿!!!阿里2024财报曝光
9411亿!!!阿里2024财报曝光
6032 0