如何用 DDD 给 DDD 建模,破解 DDD 的魔法?


prtyaa
prtyaa 2023-12-25 13:19:27 67207
分类专栏: 资讯

DDD 全称是 Domain-Driven Design,而不是我们所擅长的 Deadline-Driven Design。本来,对于再炒这一波冷饭,实在是没有啥乐趣。直到,我发现它可以炒成蛋炒饭 —— 加入 Feakin 的图形生成,适量的编译器知识,还有半勺 WASM。所以,这就是我们所要做的事件,为 DDD 建个模,基于模型生成架构图,以展示设计模型与实现的模型的差异。

众所周知,DDD 的问题域在于:如何将复杂问题控制到人能处理的范围?所以,我们要做的事情就是:

  • 采用合理的方式拆解不同场景。诸如于战略、战术分别是不同的场景。
  • 借助原则与模式解决人类智商不够的问题。诸如于图、设计模式等。
  • 采用广义 DSL (领域特定语言)来精炼语言描述。

以上就是我们在建模时的三个基本思想。

我们的问题是什么?

回到标题上,我们用 DDD 给 DDD 进行建模,只是我们想到的解决方案之一,而不是问题。先再回到上面的问题上, DDD 要解决什么问题 —— 如何将复杂问题控制到人能处理的范围?

在社区经过了几年的实践之后,已经有了文档和流程之后,接下来,就是工具化了:如何将 DDD 固化到软件设计与开发流程中?市场上已经有一系列的工具,诸如于大家经常吐槽的 COLA 做了类似的事情。

而我们想做的是:如何实现 DDD 设计与代码实现的双向绑定?于是乎,DSL 与双向图形化便是我们想到的解。所以,作为解决方案的第一步,那便是对 DDD 进行建模,以进行 DDD 的图形生成。

统一 DDD 的统一语言

尽管,我司(Thoughtworks)会在各类的 DDD 工作坊中强调,统一语言的重要性。但是,据观察,我们并没有在内部达成真正的 DDD 统一语言,只达成了一定范围内的统一语言。这大抵是形式化表示与文字化的差异,形式化会产生更强的规约,并通过它来构建一个框架。

于是乎,这里,我们采用 DDD 社区给出了一个详细的《DDD 概念参考》,作为我们构建 DDD 的统一语言的基础。只是,从名词的分类上,我更偏向于原始版本的 DDD 一书的分类:

  • 战略设计(Strategic Design)。
  • 战术设计(Tactical design)。
  • 应用模式设计。考虑到 DDD 一书本身是围绕于模式语言构建的,因此诸多内容是关于如何应用模式来改善设计。

如此一来,可以让不同的利益相关者,关注于自己所关注的部分。架构师和业务人员关注于战略设计,架构师和开发人员关注于战术设计,开发人员关注于软件设计。

战略设计的模型:DDD 自身的核心子域是什么?

在有了统一语言之后,我们就可以知道子系统-领域-子域-限界上文的关系,毫无疑问都是一对多。唯一比较有意思的是核心域支撑域通用域,如何在后续实现的时候,去设计他们呢?只是一种类型呢,还是?

那么问题来说,在上一步里,因为我们对于名词进行了分类,所以我们得到了三个子域:战略设计战术设计应用模式设计。那么,在这三个子域里,哪个是核心子域呢?

答案是,每个都是,每个也不都不是。作为一个子域,它是不是核心域,取决于你会不会观测它。“观测”这种行为,会对被观测对象造成一定影响 —— 遇事不决,量子力学。在进行 DDD 建模时,DDD 的核心域取决于 scope,也就是会出现因团队而异的场景。

战略设计的模型:如何表示上下文间的关系?

接着,我们就为到 DDD 最常被提到的上下文映射图,即用于表示一个子域内多个上下文的关系,如下图所示:

从代码化的方式来考虑,这个图并不复杂,采用形如 Graphviz 的模式就能表示:

ContextMap {
  ShoppingCarContext -> MallContext;
  ShoppingCarContext <-> MallContext;
}

唯一有意思的点在于,如何表示两个限界上下文间的关系?依然存在一系列的迷惑点。而除了协作关系之外,我们还要考虑诸多问题:诸如于它们之间是如何通信的?

战术设计的模型: 限界上下文的表示

接下来,就是表示一下限界上下文了:

一个限界上下文下,包含了多个聚合。所以,从模型的形式上,我们需要 Aggregate 这样一个容器,用于显式表达这个概念。一个聚合包含了一系统的实体,而实体和对象间存在着复杂的关系。于是乎,我们用右图来进一步表示他们的关系。聚合根(Aggregate Root)是众多实体中的一个,实体之间可能存在一定的关系。

在这时,如何用代码来表示它们,就变得非常有意思。如下是我们当前设计的一个简单的 DSL:

Aggregate ShoppingCart {
  Entity Product {
    constructor(name: String, price: Money)
  }
}

从现在的 DSL 设计来看,依旧还有很大的改进空间。

应用模式设计:如何表示?

最后,我们还有考虑的问题是,如何对 DDD 中采用的模式部分进行抽象?诸如于

  • 如何用代码化的方式,表示采用 Factory、Repository、Service、Event 等开发模式进行表示?
  • 如何将 Domain 作为能力组件向外提供服务,Application、Service、Module,还是 Package ?
  • 如何使用代码化的方式来描述分层模式?

如下图所示:

采用何种方式来表达这些模式,变成了一种很有意思的事情。当然, 这也是我们在 Feakin 中想要继续探索的内容。

DDD 的领域特定语言形式

既然,我们已经抽象到了基础的模型,那么就可以基于模型与过程,构建 DDD 的领域特定语言。

业内对于采用领域特定语言来表示 DDD 建模结果,已经相对比较成熟了,典型的方式就是:DDD DSL 与基于现有的工具扩展。

ContextMapper 与 UML

ContextMapper( contextmapper.org/)便是一个不错的 DDD DSL,虽然在语法设计上不具备概念完整性。但是,还是作为一个参考项目,还是非常不错的。采用的是 Eclipse 家族的 Xtext 作为 DSL 开发工具,唯一坑的点在于 Intellij IDEA 的 Xtext 非常难用。示例如下:

ContextMap {
    contains CargoBookingContext
    contains VoyagePlanningContext
    contains LocationContext

    CargoBookingContext Shared-Kernel VoyagePlanningContext
    CargoBookingContext Downstream-Upstream [OHS,PL]LocationContext
    LocationContext[OHS,PL] Upstream-Downstream VoyagePlanningContext

}

ConextMapper 比较遵循原书中的定义,只是在语法设计上还有很大的改进空间。

第二类,便是如在 DDD 社区的《DDD 建模工作坊指南》里采用的 UML 示例:

@startuml

namespace user-context {
   User <<Aggregate Root>>
   VerifyCode <<Aggregate Root>>
   Authorization <<Aggregate Root>>
}

namespace question-context {
  Question <<Aggregate Root>>
  Anwser <<Entity>>
  Question "1" *-- "N" Anwser
}

namespace space-context {
  Space <<Aggregate Root>>
  SpaceMember <<Entity>>
  Space "1" *-- "N" SpaceMember
  SpaceApply <<Entity>>
  Space "1" *-- "1" SpaceApply
}

@enduml

Feakin Language

于是乎,为了更好的进行 DDD 建模:图示方式 + 代码生成 + 与实现的双向绑定。我们在 feakin 内部创建了一个 FKL:fkl-parser,用于支撑软件架构的创建。采用了 Pest.rs 作为解析器生成器,现在的语法还比较简单:

declarations = _{ SOI ~ declaration* ~ EOI }

declaration = {
  context_map_decl
  | context_decl
  | ext_module_decl
  | aggregate_decl
  | entity_decl
}

context_map_decl = {
  "ContextMap" ~ identifier? ~ "{" ~ (context_node_decl | context_node_rel | inline_doc)* ~ "}"
}

...

形式上类似于 Antlr。基于此的 DSL 示例如下:

ContextMap {
  SalesContext <-> SalesContext;
}

Context SalesContext {
  Module Sales {
    Aggregate SalesOrder
  }
}

Entity SalesOrderLine {
  constructor(product: Product, quantity: Quantity)
}

当前只完成了基本的 DDD 战略和战术设计,还有应用模式设计需要考虑

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=30241
赞同 0
评论 0 条
prtyaaL0
粉丝 1 发表 2554 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1448
银河麒麟打印带有图像的文档时出错  1365
银河麒麟添加打印机时,出现“server-error-internal-error”  1151
统信桌面专业版【如何查询系统安装时间】  1073
统信操作系统各版本介绍  1070
统信桌面专业版【全盘安装UOS系统】介绍  1028
麒麟系统也能完整体验微信啦!  984
统信【启动盘制作工具】使用介绍  627
统信桌面专业版【一个U盘做多个系统启动盘】的方法  575
信刻全自动档案蓝光光盘检测一体机  483
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!