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

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

Context Map
ContextMap主要是描述各个Domain中各个BoundedContext间的关联关系,你可以理解为BoundedContext的拓扑地图。这里我们先不详细介绍BoundedContext,你现在只需要理解为实现Domain的载体,如你编写的HSF服务应用、一个处理客户请求的Web应用或者手机App,也可以是你租用的一个外部SaaS系统等。举一个例子,你的系统中有一个blog的SubDomain,你可以自行开发,也可以架设一个WordPress,或者用Medium实现Blog。回到微服务的场景,如何划分微服务应用?SubDomain对应的是业务或者虚拟的领域,而BoundedContext则是具体支持SubDomain的微服务应用,当然一个SubDomain可能对应多个微服务应用。
既然是描述各个BoundedContext关系,必然会涉及到关联关系,如DDD推荐的Partnership([P]<->[P])、Shared Kernel([SK]<->[SK])、Customer/Supplier([C]<-[S])、Conformist(D,CF]<-[U,OHS,PL])、Open Host Service、Anticorruption Layer([D,ACL]<-[U,OHS,PL])、Published Language等,详细的介绍大家可以参考DDD图书。这些对应关系都有对应的缩写,就是括号内的表述方法。这里给出关联关系Cheat Sheet说明图:



image.png

图源:https://github.com/ddd-crew/context-mapping如果你自行画图来表达这些关系,一定有非常多的工作量,细致到箭头类型,备注等,不然会引发误解。这里我们就直接上ContextMapper DSL对ContextMap的描述方式,代码如下:

ContextMap UserContextMap {
   type = SYSTEM_LANDSCAPE
   state = TO_BE
   contains AccountContext
   contains UserTagContext
   contains PaymentProfileContext
   contains SnsProfileContext
   contains ProfilesContext
   contains UserLoginContext
   contains UserRegistrationContext
    UserLoginContext [D]<-[U] AccountContext {
        implementationTechnology = "RSocket"
        exposedAggregates = AccountFacadeAggregate
    }
    ProfilesContext [D]<-[U] UserTagContext {
        implementationTechnology = "RSocket"
        exposedAggregates = UserTags
    }
    UserRegistrationContext [D,C]<-[U,S] UserTagContext {
        implementationTechnology = "RSocket"
        exposedAggregates = UserTags
    }
    UserRegistrationContext [D,C]<-[U,S] SnsProfileContext {
        implementationTechnology = "RSocket"
    }
}

大家可以看到Map图中包含的各个BoundedContext名称,然后描述了它们之间的关系。在关联关系描述中,涉及到对应的描述。前面我们说明BoundedContext为Domain的具体系统和应用的承载,所以涉及到对应的技术实现。如HTTP REST API、RPC、Pub/Sub等,如blog系统为Medium的话,那么implementationTechnology = ”REST API"。还有exposedAggregates,表示暴露的聚合信息,如class对象和字段,服务接口等,方便通讯双方做对接,这个我们会在BoundedContext中进行介绍。


BoundedContext
在ContextMap中我们描述了它们之间的关联关系,接下来我们要进行BoundedContext的详细定义。BoundedContext包含的内容相信大多数同学都知道,如Entity, ValueObject,Aggregate,Service,Repository、DomainEvent等,这个大家应该都比较熟悉。这里我们给出一个ContextMapper对BoundedContext的代码,如下:

BoundedContext AccountContext implements AccountDomain {
    type = APPLICATION
    domainVisionStatement = "Managing account basic data"
    implementationTechnology = "Kotlin, Spring Boot, MySQL, Memcached"
        responsibilities = "Account", "Authentication"
    Aggregate AccountFacadeAggregate {
       ValueObject AccountDTO {
          long id
          String nick
          String name
          int status
          Date createdAt
          def toJson();
       }
       /* AccountFacade as Application Service */
       Service AccountFacade {
          @AccountDTO findById(Integer id);
       }
    }
    Aggregate Accounts {
         Entity Account {
            long id
            String nick
            String mobile
            String ^email
            String name
            String salt
            String passwd
            int status
            Date createdAt
            Date updatedAt
         }
   }
}

这里对BoundedContext再说明一下:

  • BoundedContext的名称,这个不用说啦,这个和ContextMap中名称一致。

  • implements AccountDomain:表示要实现哪一个SubDomain,我们都知道一个Subdomain可能会包含多个BoundedContext,这些BoundedContext配合起来完成Subdomain的业务需求。ContextMap还提供refines,来表示BoundedContext要实现一些user case,官方文档有对应的说明。

  • BoundedContext的属性字段:type表示类型,如APPLICATION、SYSTEM等。domainVisionStatement描述一下BoundedContext的职责。implementationTechnology表示具体的技术,前面我们说到BoundedContext已经涉及具体的应用和系统等,所以要说明对应的技术方案实现,核心的部分描述一下就可以。responsibilities 表示BoundedContext的职责列表,这里只需要关键字就可以,如Account要负责安全验证等。

  • AccountFacadeAggregate: 表示提供给外部调用的聚合,这里DTO的对象定义、服务接口的定义等。

  • Aggregate Accounts:这个表示BoundedContext内部的聚合,如entity、value object、service等。这里说明一下,DDD中的那个Aggregate是entity,value object的聚合对象,而ContextMapper中的Aggregate表示为一些资源的集合,如Service集合等。

BoundedContext的更多信息,可以参考sculptor的文档[4],根据实际的情况可以添加对应的部分,如DomainEvent、Repository等。
个人觉得这里BoundedContext还没有涉及到Ubiquitous Language,还是需要对应的辅助设计文档,需要交代相关的项目背景,技术决策等等。个人是推荐采用C4架构设计作者编写的 《Visualise, document and explore your software architecture》[5],非常实用,作为DDD架构设计文档,完全没有问题。
文章的一开头我们说到之前的DDD DSL更多的是代码生成器,如果是代码生成器,那么生成的代码一定有对应的规范和结构等,如entity、value object,service,repository保存的目录,生成的代码可能还包括一定的Annotation或者interface,标准字段等等。当然这里我们不讨论代码生成器的问题,但我们希望大家的DDD架构设计还是要采用一定的规范目录结构,这里有几个标准推荐给大家:

  • ddd-4-java: Base classes for DDD with Java[6]

  • jDDD:Libraries to help developers express DDD building blocks in Java code[7]

  • ddd-base: DDD base package for java[8]


这三者其实出发点都是一致的,就是在代码层面来描述DDD,核心是一些annotation、interface,base class,当然也包括推荐的package结构。
ContextMapper的其他特性
讲到这里,其实DDD整体上来说,我们已经阐述清楚:Domain划分、整体Domain的BoundedContext拓扑图和关联关系、BoundedContext具体定义和架构设计文档规范。但是ContextMapper还提供了UserStory和UseCase对应的DSL,让我们来看一下。

UserStory
好多同学都问UserStory如何写,有了这个DSL,同学们再也不用担心如何编写UserStory啦。这个DSL比较明确的,主要是三元素:作为 “aaa",我希望能"xxx",我希望能”yyyy",以便 "zzz", 也是符合UserStory的典型三要素:角色、活动和商业价值。





UserStory Customers {
    As a "Login User"
        I want to update a "Avatar"
        I want to update an "Address"
    so that "I can manage the personal data."
}


UseCase
Use Case是描述需求的一种方式,在UML图就有对应的UseCase图,核心就是actor,交互动作和商业价值,对应的DSL代码如下:




UseCase UC1_Example {
  actor = "Insurance Employee"
  interactions = create a "Customer", update a "Customer", "offer" a "Contract"
  benefit = "I am able to manage the customers data and offer them insurance contracts."
}


在Aggregate聚合中,你可以设置useCases属性来描述对应的UseCase, 如下:


Aggregate Contract {
  useCases = UC1_Example, UC2_Example
}



ContextMapper带来的收益
按照你的说法,我们用DSL代码方式来描述DDD,这个有什么收益?
架构设计标准化
这种代码方式,一目了然且非常规范。如果你代码写错会有什么问题,当然是编译不通过,IDE都会帮你纠正。所以DDD DSL也是这样,完全无歧义。目前ContextMapper DSL包括Eclipse和VS Code插件,在IntelliJ IDEA可以通过自定义File Types和Live template方式来辅助你编写cml文件。
生成器(Generators)支持
前面我们聊到DDD DSL支持代码生成器,可以辅助你生成代码,相信这个大家都能明白,因为DDD DSL代码是标准的,基于这个Code Model生成其他形式的代码,这个当然可以。
另外ContextMapper还支持其他模型生成,如ContextMap图形化展现、PlantUML的结构图,对应的代码在这里[9]。我这里给大家一些截图:





image.png


image.png


image.png


当然ContextMapper还提供通用的生成器,也就是基于DDD DSL模型,加上Freemarker模板,然后就可以生成你想要的各种输出,如生成JHipster Domain Language (JDL)用于快速创建文件脚手架也不奇怪。相信很多Java程序员对此都不陌生,我们开发Web应用时就是使用Freemarker生成HTML的。更多细节访问这里[10]。



相关文章
|
2月前
|
消息中间件 测试技术 领域建模
DDD - 一文读懂DDD领域驱动设计
DDD - 一文读懂DDD领域驱动设计
478 0
|
9月前
|
消息中间件 架构师 搜索推荐
DDD领域驱动设计的概念解析
DDD领域驱动设计的概念解析
161 0
|
11月前
|
前端开发 架构师 Java
领域驱动设计DDD从入门到代码实践
在本文中,作者将借鉴《实现领域驱动设计》的做法,介绍领域驱动设计的基本概念的同时,用一个虚拟的公司和一个虚拟的项目,把领域驱动设计进行落地实践。
11197 9
领域驱动设计DDD从入门到代码实践
|
前端开发 API 网络架构
DDD as Code:如何用代码诠释领域驱动设计?(3)
DDD as Code:如何用代码诠释领域驱动设计?
128 0
|
Kubernetes 前端开发 架构师
DDD as Code:如何用代码诠释领域驱动设计?(1)
DDD as Code:如何用代码诠释领域驱动设计?
141 0
|
前端开发 JavaScript NoSQL
DDD实战之二:看看代码结构长啥样
DDD实战之二:看看代码结构长啥样
DDD实战之二:看看代码结构长啥样
|
项目管理
DDD案例(1):从需求分析到领域分析(4)
DDD案例(1):从需求分析到领域分析(4)
424 0
DDD案例(1):从需求分析到领域分析(4)
|
设计模式 缓存 Java
DDD之代码架构
这是一篇迟到的文章。这其实是我写DDD的第四篇文章。去年11月份左右我在个人网站上写了三篇关于DDD的文章,都是比较偏战略部分的。那个时候我还在一个正在使用DDD的项目上,也是我第一次真正开始深入使用DDD。
552 1
DDD案例(1):从需求分析到领域分析(5)
DDD案例(1):从需求分析到领域分析(5)
230 0
DDD案例(1):从需求分析到领域分析(5)
|
供应链 数据可视化 Java
DDD案例(1):从需求分析到领域分析(1)
DDD案例(1):从需求分析到领域分析(1)
454 0
DDD案例(1):从需求分析到领域分析(1)