0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

DDD驱动如何设计?如何进行领域建模?

jf_ro2CN3Fa 来源:技术琐话 2023-07-18 14:11 次阅读

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的数据结构,也就是所谓的“贫血模式”。其代码如下所示:

publicclassMoneyTransferServiceTransactionScriptImpl
implementsMoneyTransferService{
privateAccountDaoaccountDao;
privateBankingTransactionRepositorybankingTransactionRepository;
...
@Override
publicBankingTransactiontransfer(
StringfromAccountId,StringtoAccountId,doubleamount){
AccountfromAccount=accountDao.findById(fromAccountId);
AccounttoAccount=accountDao.findById(toAccountId);
...
doublenewBalance=fromAccount.getBalance()-amount;
switch(fromAccount.getOverdraftPolicy()){
caseNEVER:
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()方法。

publicclassAccount{
privateStringid;
privatedoublebalance;
privateOverdraftPolicyoverdraftPolicy;
...
publicdoublebalance(){returnbalance;}
publicvoiddebit(doubleamount){
this.overdraftPolicy.preDebit(this,amount);
this.balance=this.balance-amount;
this.overdraftPolicy.postDebit(this,amount);
}
publicvoidcredit(doubleamount){
this.balance=this.balance+amount;
}}

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

publicinterfaceOverdraftPolicy{
voidpreDebit(Accountaccount,doubleamount);
voidpostDebit(Accountaccount,doubleamount);}publicclassNoOverdraftAllowedimplementsOverdraftPolicy{
publicvoidpreDebit(Accountaccount,doubleamount){
doublenewBalance=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) {
  }}

而Domain Service只需要调用Domain Entity对象完成业务逻辑。

publicclassMoneyTransferServiceDomainModelImpl
implementsMoneyTransferService{
privateAccountRepositoryaccountRepository;
privateBankingTransactionRepositorybankingTransactionRepository;
...
@Override
publicBankingTransactiontransfer(
StringfromAccountId,StringtoAccountId,doubleamount){
AccountfromAccount=accountRepository.findById(fromAccountId);
AccounttoAccount=accountRepository.findById(toAccountId);
...
fromAccount.debit(amount);
toAccount.credit(amount);
BankingTransactionmoneyTransferTransaction=
newMoneyTranferTransaction(fromAccountId,toAccountId,amount);
bankingTransactionRepository.addTransaction(moneyTransferTransaction);
returnmoneyTransferTransaction;
}}

通过DDD重构后,虽然类的数量比以前多了一些,但是每个类的职责更加单一,代码的可读性和可扩展性也随之提高。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://github.com/YunaiV/yudao-cloud

视频教程:https://doc.iocoder.cn/video/

7.3数据驱动和领域驱动

7.3.1数据驱动

目前主流的开发模式是由数据驱动的。数据驱动的开发很容易上手,

有了业务需求,创建数据库表,然后编写业务逻辑,开发过程如图7-1所示。数据驱动以数据库为中心,其中最重要的设计是数据模型,但随着业务的增长和项目的推进,软件开发和维护的难度会急剧增加。

a6587c8e-2531-11ee-962d-dac502259ad0.png

图7-1数据驱动研发过程

以客户关系管理(Customer Relationship Management,CRM)为例,其中很重要的概念有销售、机会、客户、私海、公海,实体的定义分别如下。

销售(Sales):公司的销售人员,一个销售可以拥有多个销售机会。

机会(Opportunity):销售机会,每个机会包含至少一个客户信息,且归属于一个销售人员。

客户(Customer):客户,也就是销售的对象。

私海(Private sea):专属于某个销售人员的领地(Territory),私海里面的客户,其他销售人员不能触碰。

公海(Public sea):公共的领地,所有销售人员都可以从公海里捡入客户到其私海。

按照我们曾经学习的数据库建模理论,对于上面的场景,不难画出图7-2所示的实体联系(Entity Relationship,ER)图。

a6656a02-2531-11ee-962d-dac502259ad0.png

图7-2CRM的ER图

可以看到,图7-2所示的ER图中不存在公海和私海,因为所谓的机会在私海,就是这个机会是不是归属某个销售,这样我们只需要看机会上是否有salesId。如果有,说明机会被某个销售占有,也就是在私海中;反之,这个机会就在公海中。

在这种开发模式下,最后的产出是几张数据库表,以及针对表中数据进行操作的事务脚本,如图7-3所示。

a67ffa20-2531-11ee-962d-dac502259ad0.png

图7-3事务脚本实现

7.3.2领域驱动

领域驱动设计关心的是业务中的领域划分(战略设计)和领域建模(战术设计),其开发过程不再以数据模型为起点,而是以领域模型为出发点,研发过程如图7-4所示。领域模型对应的是业务实体,在程序中主要表现为类、聚合根和值对象,它更加关注业务语义的显性化表达,而不是数据的存储和数据之间的关系。 这是“领域驱动设计”和“数据驱动设计”之间显著的区别。

a68bec2c-2531-11ee-962d-dac502259ad0.png

图7-4领域驱动研发过程

仍以上面的CRM为例。假如我们先不考虑数据模型,而是采用面向对象分析(Object Oriented Analysis,OOA)对这个场景进行领域建模,那么可以得到图7-5所示的领域模型。

a6a14a4a-2531-11ee-962d-dac502259ad0.png

图7-5CRM的领域模型

可以看到,在图7-5中,领域模型的描述更加贴近业务,一些重要的业务术语和概念没有丢失,更完整地表达了业务语义。即使是产品经理或者业务人员,也不难看懂这样的领域模型,甚至他们可以和技术人员一起参与到梳理领域模型和创建活动中来。

通过DDD的战略设计和战术设计,我们可以为问题域划分出合适的子域,并对域中的业务进行建模。图7-6所示是我们在实际工作中为CRM进行的领域战略设计。

a6ba018e-2531-11ee-962d-dac502259ad0.jpg

图7-6CRM的领域划分

7.3.3ORM

很明显,领域模型和数据模型并不是一一对应的关系,但也不排除,有些情况领域模型和数据模型是趋同的,但是大部分情况都需要做一层映射(Mapping)。为了弥补二者之间的差异,行业先驱们做了很多关于映射工作的尝试,这种技术有一个名称叫作对象关系映射(Object Relationship Mapping,ORM),如图7-7所示。

a6d87678-2531-11ee-962d-dac502259ad0.png

图7-7对象关系映射

ORM曾经非常火,记得当年Hibernate才出现时,我用尽了其中的高级技巧,比如继承关系映射、多对多关系映射……结果弄出来的东西却变成了“四不像”,既不像Entity,也不像数据对象(Data Object,DO)。

ORM的问题在于它太理想化,期望通过工具把数据建模和领域建模合一,这样的尝试注定是很难成功的。仍以上述的CRM案例为例,在数据模型中根本就没有私海和公海这两个实体,工具是无法映射的。因此,Hibernate和JPA的衰落是可以预见的。现在使用最多的是MyBatis,它很简单,完全不理会复杂的关系和对象之间的复杂关系映射,只做数据库表和DO之间的简单映射。

复杂的数据库关系和对象关系之间的差异,其本质是数据模型和领域模型之间的差异,而这种差异的多样性和灵活性是很难通过规则预先定义的,这也是为什么工具的作用会很有限。现在的互联网大厂大多使用MyBatis,原因也在于此。因此,如果你打算实践DDD,请一定不要让工具帮你去建模,工具不会抽象,也不会思考,还是要老老实实自己动手去建。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • CRM
    CRM
    +关注

    关注

    1

    文章

    143

    浏览量

    20992
  • 数据驱动器
    +关注

    关注

    0

    文章

    5

    浏览量

    6151
  • ddd
    ddd
    +关注

    关注

    0

    文章

    21

    浏览量

    2736

原文标题:DDD的精髓

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Android APP如何进行访问硬件驱动

    本文我们要讲的是在用 i.MX8 平台开发时,Android APP 如何进行访问硬件驱动
    的头像 发表于 12-04 13:50 484次阅读
    Android APP如<b class='flag-5'>何进行</b>访问硬件<b class='flag-5'>驱动</b>

    何进行FSM任意状态建模

    两个快速问题:1)我有以下状态图:如您所见,一些状态转换是明确确定的,一些转换是任意的。例如,当处于空闲状态时,下一状态可以是就绪状态或保持空闲状态。我怎样才能在VHDL中对此进行建模(不是一切
    发表于 10-31 07:18

    3D建模软件中如何进行装配设计?

    地提高装配效率。▲阵列操作▲镜像操作5、装配体修改通常情况下,使用浩辰3D建模软件对装配体进行修改,主要分为两个方面:一个是配合调整,另一个是装配层级修改。① 配合调整:选择需要修改配合关系的零部件
    发表于 03-12 14:48

    基于领域建模的数控系统代码生成技术

    为提高数控软件系统开发效率,提出基于领域建模的代码自动生成开发方法。该方法在数控领域元模型的基础上建立了数控系统的建模环境,以模型数据到源代码的映射规则库为基
    发表于 04-02 08:36 11次下载

    综合航电领域建模技术研究

    为了方便对综合化航空电子系统的领域建模,通过对综合航电系统的领域分析,提出了综合航电系统的参考模型;在此基础上,使用通用建模环境GME设计了综合航电系统的系统级、模块级、分区级以及进程级的元模型
    发表于 01-04 14:55 0次下载

    黑客攻防入门与进阶ddd

    黑客攻防入门与进阶ddd黑客攻防入门与进阶ddd
    发表于 02-23 15:45 8次下载

    详解领域驱动设计和spring

    领域驱动设计 Eric Evans的《领域驱动设计》无疑是软件设计领域最重要的几本书之一。 这本书主要集中在软件开发中如何处理
    发表于 09-27 12:51 0次下载
    详解<b class='flag-5'>领域</b><b class='flag-5'>驱动</b>设计和spring

    详解领域驱动设计和spring

    领域驱动设计 Eric Evans的《领域驱动设计》无疑是软件设计领域最重要的几本书之一。 这本书主要集中在软件开发中如何处理
    发表于 09-27 12:51 0次下载
    详解<b class='flag-5'>领域</b><b class='flag-5'>驱动</b>设计和spring

    SIMBOSS:物联网业务如何应用领域驱动设计

    物联网业务如何应用领域驱动设计?这其实是个大命题,该怎么实现?如何下手呢?我找了我之前做的一个业务需求,来做示例,看看“脚本式”的实现,和 DDD 的实现,前后有什么不太一样的地方。
    的头像 发表于 12-25 18:31 441次阅读

    何进行OPCDCOM配置

    何进行OPCDCOM配置(四会理士电源技术有限公司招聘)-如何进行OPCDCOM配置                      
    发表于 09-18 14:23 11次下载
    如<b class='flag-5'>何进行</b>OPCDCOM配置

    SystemVerilog对硬件功能如何进行建模

    本文定义了通常用于描述使用SystemVerilog对硬件功能进行建模的详细级别的术语。
    的头像 发表于 03-30 11:42 1374次阅读

    何进行电源设计 - 第1部分

    何进行电源设计 - 第1部分
    发表于 11-02 08:16 1次下载
    如<b class='flag-5'>何进行</b>电源设计 - 第1部分

    用好DDD必须先过Spring Data这关

    DDD 是一种领域驱动的设计方法,旨在通过建立对领域模型的清晰理解来解决业务问题。和事务脚本不同,DDD 使用面向对象设计来应对复杂的业务场
    的头像 发表于 03-07 09:38 1836次阅读

    一文理解DDD领域驱动设计

    2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD
    的头像 发表于 05-25 14:21 651次阅读
    一文理解<b class='flag-5'>DDD</b><b class='flag-5'>领域</b><b class='flag-5'>驱动</b>设计

    DDD是什么?DDD核心概念梳理

    DDD 是什么,DDD 的英文全称是 Domain-Driven Design,翻译过来就是领域驱动设计。
    的头像 发表于 09-07 11:12 3430次阅读
    <b class='flag-5'>DDD</b>是什么?<b class='flag-5'>DDD</b>核心概念梳理