DDD as Code:如何用代码诠释领域驱动设计?(1)

简介: DDD as Code:如何用代码诠释领域驱动设计?

image.png

相较于常规的MVC架构,DDD更抽象、更难以理解,各个开发者对DDD的解释也不尽相同。那么哪种设计方式才更好?在学习时如何知道哪种DDD更正统,没有被别人带歪?本文尝试使用“DDD as Code”的概念,即用DSL代码方式来描述DDD,统一DDD的设计思想,通过案例详细介绍如何基于ContextMapper来完成一个项目基于DDD DSL的表达,并分享现实中DDD的设计流程和微服务的关系。


网上有非常多关于DDD的文章,这当然是好事情,大家都想掌握好的设计方法来解决软件开发中的问题。但是这其中也存在一些问题,如果你随便打开网上的几篇DDD文章,虽然每一位作者都说自己是按照DDD思路进行架构设计的,但是细心的同学会发现每一个作者DDD文章中的结构描述、画的架构图都千差万别,你会非常奇怪,这些都是DDD设计吗?这里其实有一个问题,就是通过文字和图示描述一些抽象的概念时,本来就会有很大的差别。大家不要用盲人摸象的概念进行类比,这个不太合适,即便两个同学,对DDD都非常了解,而且都实践了好几年多个项目,他们写出来的东西还是不一样。我Java入行稍微早点,当然你说我年纪大保守也可以,记得当初没有那么多中间件,就是基于Struts 1.x这个MVC框架进行开发,不同的同学写出的设计文档也是千差万别。这么简单的MVC架构都能有不同的架构设计文档,而DDD相对更抽象、更难以理解,所以架构设计文档长的不太一样,这个也是可以理解的。
那么我们是不是要接受这个事实,“各个作者对DDD的解释可以不必相同”,"DDD设计文档可以以不同种形式呈现"?如果是这样,那么想学习DDD的同学就有非常大的负担,哪种设计表现方式才是比较好的,才是比较容易理解的,同时我怎么知道我学的DDD是相对正统的,没有被别人带歪。我不是说发挥性思考不可以,但是从传道的角度来说,尊重理论事实还是需要的。
我们都知道代码在表达一些业务或者逻辑时,非常能反应真实情况,即便是不同的开发者所编写,考虑到遵循Design Pattern、命名规范、开发语言约束等,代码大体上还是相同的,还是便于理解的,如果有单元测试和Code Review,那就更好啦。这也是在一些文档不完善的时候,非常多同学选择阅读代码,更有同学说,“直接看代码,不要看他们PPT和文档,会被误导的,不然怎么死的都不知道”。另外我们都知道,现在一个非常好的实践就是Everything as Code,典型的如Infrastructure as Code的Terraform,Platform as code的Kubernetes YAML, Diagram as Code的PlantUML等等, 那么我们能否使用DDD as Code这个概念,让我们的设计更统一些,更能方便表达设计思想,更容易被人理解。
DDD DSL
用DSL也就是用代码方式来表达DDD,这个很早就有了,但是更偏向DDD的战术设计(Tactic Design)和代码层面,如 Sculptor[1]和fuin.org DDD DSL[2] ,大家普遍都认为,就是基于Xtext的DDD代码生成器。要费劲学习那么多,只为生成一些代码,而且只是Java代码,所以普遍关注度并没有多高。
我们能否将DDD DSL除了代码生成这一部分,更偏向于战略设计(Strategic Design),突出设计的思想,那么DDD as Code就全面多了。接下来我们就介绍ContextMapper这个框架。
名词解释一下:有不少同学对于DDD的战略设计(Strategic Design) 和战术(Tactic Design)之间区分有些疑惑,DDD有专门的介绍,如下:

  • 战术设计(Tactic DDD):Entity, Value Object; Aggregate, Root Entity, Service, Domain Event; Factory, Repository。


  • 战略设计(Strategic DDD):Bounded Context, Context Map; Published Language, Shared Kernel, Open Host Service, Customer-Supplier, Conformist, Anti Corruption Layer (context relationship types)。


其实也比较简单,战略设计更大一些,偏宏观,你可以理解为公司高层在讨论的业务和技术方向,各个团队或者产品的分工和配合;而战术设计则相对小很多,主要集中在一个BoundedContext内部,比如如何设计DDD那些Entity,Service,Repository等,外加可能的应用开发的技术选型,可以说更关注技术层面。
ContextMapper框架介绍
ContextMapper是一个开源项目[3] ,主要是为DDD设计提供DSL支持,如DDD的战略(Strategic)设计,Context间映射(Context Mapping),BoundedContext建模以及服务解耦(Service Decomposition),那么我们就看一下如何基于ContextMapper来完成一个项目基于DDD DSL的表达。
在介绍ContextMapper时,我们先交代一下项目背景。如花是一名架构师,对DDD也非常熟悉,而且有过几个项目的DDD实践,最近他加入会员线,负责完成对会员系统的改造,更好地配合公司的微服务化的设计思路。会员线之前就是三个应用:会员中心对外提供的大量的REST API服务;会员注册和登录应用;会员中心,处理会员登录后如修改个人密码、基本信息、SNS第三方绑定和支付方式绑定等。
如花加入会员团队后,和大家沟通了基于DDD + MicroServices的架构思想,大家都表示同意,但是如何落实到具体的架构设计和文档上,大家就犯难啦。让我们看一下最典型的DDD设计图:


image.png


其中的概念,如SubDomain、BoundedContext、Entity、ValueObject、Service、 Repository、Domain Event,以及Context映射关系(Context Mapping),这些都没有问题,但是如何向他人表达这个思想?总不能每次都把DDD设计图和分层图都贴上去,然后说我就是按照DDD设计的。
从SubDomain开始
如花开始DDD的第一步,也就是Subdomain的划分。当然DDD中包括三种类型的SubDomain,分别为通用(Generic)、支撑(Supporting)和核心(Core)三种类型,这里稍微说明一下这几者的区别:

  • 通用(Generic) Domain:  通用Domain通常被认为已经被行业解决的问题,如架构设计中的可观测性的Logging、Metrics和Tracing,各种云服务(Cloud Service)等,这些都已经有比较好的实现方案,对接就可以。当然业务上也有,如成熟的行业解决方案,如ERP、CRM、成熟硬件系统等,你购买就可以啦。


  • 支撑(Supporting) Domain:和通用Domain类似,但是系统更来自内部或者还需要在通用的基础上进行一些定制开发。如一个电商系统,会员、商品、订单、物流等业务系统,当然还有一些内部开发的技术类型支撑系统。


  • 核心(Core) Domain:  也就是我们常说的业务核心,当然如果是技术产品,就是技术核心,这个也就是你最要关注的。


这三者整体关系如下:Core是最与众不同且花费精力比较多的,在复杂性Y维度,我们要避免高复杂度的通用和支撑Domain,这样会分散你的注意力,同时还要投入非常大的精力,如果确实需要,购买服务的方式可能最佳。
image.png


图源:https://github.com/ddd-crew/ddd-starter-modelling-process

如花首先将会员先划分为几个Sub Domain,如处理账号相关的Account,处理会员打标的UserTag,处理支付方式的PaymentProfile,处理社交平台集成的SnsProfile,还有一个其他Profiles,这里我们不涉及Generic和Supporting Doman的规划,主要从业务核心Domain出发。一个同学用PPT阐述了划分结构和出发点,如下:

image.png


但是也有同学说是不是UML的Component图更好一些,方便和后面的UML图统一,如下:


image.png


当然还有其他如Visio等非常多的图示工具用于展现结构图。DDD的第一步:SubDomain的划分和展现,就有不同的理解方式,如何描述、如何图形化展现,都有不少的分歧。
回到问题的出发点,我们就想划分一下SubDomain,那么是不是下述的DSL代码也可以:

Domain User {
    domainVisionStatement = "User domain to manage account, tags, profiles and payment profile."
    Subdomain AccountDomain {
       type = CORE_DOMAIN
       domainVisionStatement = "Account domain to save sensitive data and authentication"
    }
    Subdomain UserTagDomain {
       type = GENERIC_SUBDOMAIN
       domainVisionStatement = "UserTag domain manage user's KV and Boolean tag"
    }
    Subdomain PaymentProfileDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User payment profile domain to manage credit/debit card, Alipay payment information"
    }
    Subdomain SnsProfileDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User Sns profile domain to manage user Sns profile for Weibo, Wechat, Facebook and Twitter."
    }
    Subdomain ProfilesDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User profiles domain to manage user basic profile, interest profile etc"
    }
}



虽然目前我们还不知道对应的DSL代码语法,但是我们已经知道Domain的名称、domain类型以及domain的愿景陈述(visionStatement),至于后期以何种方式展现系统Domain,如表格、图形等,这个可以考虑基于现在的数据进行展现。其中的UserTagDomain类型为GENERIC_SUBDOMAIN,这个表示打标是通用性Domain,如我们后期可以和商品、图片或者视频团队合作,大家可以一起共建打标系统。
注意:Subdomain不只是简单包括type和domainVisionStatement,同时你可以添加Entity和Service,其目的主要是突出核心特性并方便你对Domain的理解,如Account中添加resetPassword和authBySmsCode,相信大多数人都知道这是什么含义。但是注意不要将其他对象添加到Subdomain,如VO, Repository, Domain Event等,这些都是辅助开发的,应该用在BoundedContext中。

Subdomain AccountDomain {
       type = CORE_DOMAIN
       domainVisionStatement = "Account domain to save sensitive data and authentication"
       Entity Account {
         long id
         String nick
         String mobile
         String ^email
         String name
         String salt
         String passwd
         int status
         Date createdAt
         Date updatedAt
       }
      Service AccountService {
          void updatePassword(long accountId, String oldPassword, String newPassword);
          void resetPassword(long acountId);
          boolean authByEmail(String email, String password);
          boolean authBySmsCode(String mobile, String code);
      }
    }
相关文章
|
5月前
|
中间件 BI 测试技术
【实践篇】领域驱动设计:DDD工程参考架构
领域驱动设计(DDD)参考架构旨在为团队提供DDD实践的起点,强调业务与技术的分离,考虑多种架构风格如分层、六边形等。它包括多限界上下文结构,每个上下文内有应用层(不含领域逻辑)、领域层(含领域模型和事件)和网关层。接入层负责外部请求的处理,业务层协调不同上下文。组件包括Start(启动)、Common(通用)、API、Facade、Application Service、External API、Query、Domain和Gateway,各组件有明确的职责和依赖关系,如Gateway处理技术细节并作为系统与外部的接口。架构设计是多因素权衡,适应实际工程需求。
267 0
|
前端开发 架构师 Java
领域驱动设计DDD从入门到代码实践
在本文中,作者将借鉴《实现领域驱动设计》的做法,介绍领域驱动设计的基本概念的同时,用一个虚拟的公司和一个虚拟的项目,把领域驱动设计进行落地实践。
13474 9
领域驱动设计DDD从入门到代码实践
|
消息中间件 架构师 搜索推荐
DDD领域驱动设计的概念解析
DDD领域驱动设计的概念解析
249 1
|
前端开发 API 网络架构
DDD as Code:如何用代码诠释领域驱动设计?(3)
DDD as Code:如何用代码诠释领域驱动设计?
167 0
|
IDE Java 程序员
DDD as Code:如何用代码诠释领域驱动设计?(2)
DDD as Code:如何用代码诠释领域驱动设计?
253 0
|
前端开发 JavaScript NoSQL
DDD实战之二:看看代码结构长啥样
DDD实战之二:看看代码结构长啥样
DDD实战之二:看看代码结构长啥样
|
设计模式 缓存 Java
DDD之代码架构
这是一篇迟到的文章。这其实是我写DDD的第四篇文章。去年11月份左右我在个人网站上写了三篇关于DDD的文章,都是比较偏战略部分的。那个时候我还在一个正在使用DDD的项目上,也是我第一次真正开始深入使用DDD。
627 1
|
设计模式 领域建模
DDD的模式与实践案例(1)
DDD的模式与实践案例(1)
1033 0
DDD的模式与实践案例(1)
|
设计模式
DDD的模式与实践案例(2)
DDD的模式与实践案例(2)
737 0
DDD的模式与实践案例(2)
|
Dubbo Java 应用服务中间件
DDD的模式与实践案例(4)
DDD的模式与实践案例(4)
1126 0
DDD的模式与实践案例(4)