Redian新闻
>
如何用 DDD 给 DDD 建模,破解 DDD 的魔法?

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

科技

“所有模型都不对,但总有一些是有用的。” —— George Box

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 的模式就能表示:

  1. ContextMap {

  2.   ShoppingCarContext -> MallContext;

  3.   ShoppingCarContext <-> MallContext;

  4. }

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

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

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

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

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

  1. Aggregate ShoppingCart {

  2.   Entity Product {

  3.     constructor(name: String, price: Money)

  4.   }

  5. }

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

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

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

  • 如何用代码化的方式,表示采用 Factory、Repository、Service、Event 等开发模式进行表示?

  • 如何将 Domain 作为能力组件向外提供服务,Application、Service、Module,还是 Package ?

  • 如何使用代码化的方式来描述分层模式?

如下图所示:


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


DDD 的领域特定语言形式

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

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

ContextMapper 与 UML

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

  1. ContextMap {

  2.   contains CargoBookingContext

  3.   contains VoyagePlanningContext

  4.   contains LocationContext


  5.   CargoBookingContext Shared-Kernel VoyagePlanningContext

  6.   CargoBookingContext Downstream-Upstream [OHS,PL]LocationContext

  7.   LocationContext[OHS,PL] Upstream-Downstream VoyagePlanningContext


  8. }

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

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

  1. @startuml


  2. namespace user-context {

  3.   User <<Aggregate Root>>

  4.   VerifyCode <<Aggregate Root>>

  5.   Authorization <<Aggregate Root>>

  6. }


  7. namespace question-context {

  8.   Question <<Aggregate Root>>

  9.   Anwser <<Entity>>

  10.   Question "1" *-- "N" Anwser

  11. }


  12. namespace space-context {

  13.   Space <<Aggregate Root>>

  14.   SpaceMember <<Entity>>

  15.   Space "1" *-- "N" SpaceMember

  16.   SpaceApply <<Entity>>

  17.   Space "1" *-- "1" SpaceApply

  18. }


  19. @enduml

Feakin Language

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

  1. declarations = _{ SOI ~ declaration* ~ EOI }


  2. declaration = {

  3. context_map_decl

  4. | context_decl

  5. | ext_module_decl

  6. | aggregate_decl

  7. | entity_decl

  8. }


  9. context_map_decl = {

  10.   "ContextMap" ~ identifier? ~ "{" ~ (context_node_decl | context_node_rel | inline_doc)* ~ "}"

  11. }


  12. ...

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

  1. ContextMap {

  2.   SalesContext <-> SalesContext;

  3. }


  4. Context SalesContext {

  5.   Module Sales {

  6.     Aggregate SalesOrder

  7.   }

  8. }


  9. Entity SalesOrderLine {

  10.   constructor(product: Product, quantity: Quantity)

  11. }

当前只完成了基本的 DDD 战略和战术设计,还有应用模式设计需要考虑。如果你也有兴趣的话,欢迎来加入我们。

小结

我不并擅长建模,我一直觉得模型在重构的过程中,自然而然就会浮现出来。而除了重构的这种方式,还有一种额外的方式是借助 DSL(领域特定语言)进行抽象。所以,我尝试以此作为一些出发点,借而来 Driven 中系统的模型。与得到一个有用的结果相比,在过程中对于 DDD 的抽象,构建 DDD 的 DDD 模型,显得更有意思。

如果你对于使用 DSL 作为协作设计有兴趣,欢迎一起来用电发电:https://github.com/feakin/feakin。


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
KKR面试官让我当场估值建模,还必须用LBO模型!我慌了…意外走红的魔性学霸,都有哪些校友?天凉好个秋,西雅图这10家独立书店,拥有让时间慢下来的魔法柚子跟团游是个“体力活”用魔法打败魔法!一个叫板顶级人类棋手的围棋AI输给了它的同类用魔法打败魔法!“生了个物”“化了个学”……哈哈哈教科书式回怼网络杠精!!用魔法打败魔法哈哈哈哈佛被控歧视案开庭!“用魔法打败魔法”能让亚裔年轻人未来可期吗?3行代码建模,训练速度提升200%?这款时序开源神器PaddleTS太强了!Rhino建模,在设计院已成定局!酸甜苦辣时令汤筑格精选|如何用logo营销,如何靠设计溢价?这套绘本里的小动物,都会“温暖的魔法”LA喜提降温!全美多地高温破记录的魔鬼夏天终于结束了…王冠的魔力:现代化的君主制与英国的“秘密国家”Gucci缔造的「双生之境」,是现实还是魔法?哆啦A梦的凝视:一张表情包的魔改狂潮与技术伦理Belden案 | 新加坡PDPC:如何认定员工数据跨境违法?附处罚决定书全文11岁小女孩自学3D建模,实现盲盒自由,吊打设计师!10年花10个亿,这是深圳的魔幻神作,还是最丑地标?"明星"黑猩猩的魔幻一生:被伪造死亡,被囚禁地牢,又被间谍解救重见阳光……中国失控,引发金融危机?用魔法打败魔法,广西群众太牛逼!SU建模,能不用插件吗!背靠抖音超6亿日活用户,火山引擎如何用AR改写“新消费时代”玩法?课程 |糖尿病平时如何吃?如何用药效果更好?傀儡蔡英文与食人魔佩洛西的魔鬼交易这群人,破解了Web3的「存力」难题《黑袍纠察队》其实就是在用魔法打败魔法。多伦多周边必去8大迷人小镇! 走进秋日童话, 享受美食+美酒+美景的魔力摄影欣赏:渔歌子(2):云雾苍茫行九霄逃离卷上天的魔都,我陪女儿求学新加坡、英国,有些大实话不吐不快正是这座古老的魔法之城给了她灵感...才诞生了《哈利波特》Powerball头奖攀升至16亿美元,破纪录史上最高,今晚开奖,试试你的手气如何
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。