这里是Z哥的个人公众号
每周五11:45 按时送达
当然了,也会时不时加个餐~
我的第「205」篇原创敬上
大家好,我是Z哥。
我从 2014 年开始接触 DDD 到现在也有 7 年多时间了,在这个期间踩过很多坑,也是自己慢慢从充满疑问,然后一步一步摸索着到如今可以轻松落地 DDD 的地步。
在 2014 年那会,DDD 的社区远不如现在那么繁荣,甚至还有很多鄙视它的声音。因此网上的资料也很少,大部分的知识获取源自两本书《领域驱动设计 软件核心复杂性应对之道》和《实现领域驱动设计》。
所以,我的 DDD 之路和现在的很多人可能不太一样,我是沿着一个书本上浓缩后的核心思想,然后自下而上地通过自己的摸滚打爬慢慢构建的知识体系,而现在很多人是照着一些课程中教的偏实践型的知识,自上而下的模仿,然后再根据自己的理解和场景做出调整优化。
最近几年 DDD 一直很火,网上的课程也有很多。但是说实话,DDD 里面的大多数概念还是很晦涩的,没有那种特别清晰规则,可以像代码规范那样明确指导我们如何 coding,什么是好代码,什么是坏代码。
很多小伙伴从我之前在博客园写的 DDD 系列找过来,和我交流 DDD 相关的问题,发现大家对其中的不少概念还存在一些疑惑。或者说,有些概念自己觉得清楚了,但是落地的时候好像又不知道从何下手。
今天我们就来聊聊我收集到的一些大家的困惑,来和大家交流一下我的观点。
/01 DDD 必须要配合微服务一起用?/
这个问题还有另一种问法,“单体应用能用 DDD 吗?”。
先说结论,必须可以,不是微服务也可以用。
我觉得大家之所以形成这个误区背后的逻辑是这样的。因为 DDD 提倡构建领域模型,划定限界上下文,而限界上下文在微服务中恰好能体现为一个单独的Service,看上去是如此的天衣无缝,它们俩是一组黄金搭档,就该一起使用。
其实,在我看来这两者之间的关系并不是搭档关系,而是一种“道”和“术”的关系。DDD 是一种架构设计方法论,而微服务是一种具体的架构设计实践方案。前者是“道”,后者是“术”,符合某一个“道”的“术”从来都不只有一个。
可能你要问了,单体应用怎么用 DDD ?建议你可以尝试用模块的概念来划定限界上下文,并且将不同的模块分别独立一个包,以此达到类似 Service 的隔离效果。剩下的建模工作就没什么不同了。
/02 限界上下文只能通过拆分成独立的 Service 体现?/
这个问题其实在第一点里已经解答了,不用拆分成独立的 Service 也可以体现限界上下文。
如果你存在这个问题的困惑,大概率是你在平时的开发中没有带着「上下文」这个概念去 coding。
因为我们设计的每一个模型,其实都是对现实世界的抽象,而现实世界中的每一个概念都有它所对应的上下文,脱离上下文任何一个词都有歧义。哪怕是相同的一个词在不同的上下文里表达的含义可能不同,比如,售票系统的中的“座位”与设计电影院的规划系统里的“座位”并不是同一个东西。
/03 业务简单的项目不适合 DDD ?/
这个问题其实就类似于,面积小的房子做装修是不是不需要精心设计?只有那些高大上的别墅、甚至是大型场馆才需要精心设计?
我想答案是显而易见的。
不管是简单的项目还是复杂的项目,他们都不影响良好的抽象设计给项目带来的好处。
不过有些人可能纠结的点在于与 DDD 配套的技术框架、基础设施太复杂。我觉得这个想法就有点本末倒置了。不管是什么技术框架、组件,本身只是一种工具。你拿微服务的那套放到一个简单的单体应用里自然会觉得复杂。但是我们相比回到使用传统的三层架构,更应该是要找到,甚至是自己打造更合适的工具。
/04 Entity 的属性字段需要对应数据表吗?/
我认为这个问题的答案首先有一点是确定的,就是:不像三层架构那样,完全一一对应。
因为 Entity 里面可能会嵌套其它的 Entity,以及 ValueObject。所以里面的属性数量大多会比数据表的字段更多。
另外,我觉得这里最关键的问题是,Entity 的唯一标识该如何体现。
- 是用一个 Guid ?还是直接业务标识?
- 如果不用 Guid ,那么值对象如果需要持久化的话,该怎么办?
我自己在实践的时候的标准是,尽量只用业务标识,如果必须要用 Guid,确保它是不可修改,甚至是不可见的。因为 Guid 并不是领域知识的一部分,它只是为了解决技术层面问题而引入的一个东西。
/05 Application、DomainService 的区别?/
很多人不重视 DomainService 的设计,其实它非常重要。因为在领域知识中必然存在很多「行为」不属于任何一个 Entity。比如电商领域中的付款这个动作,到底是放在订单的 Entity 上?还是账户的 Entity 上?其实你会发现都不太适合。此时就是 DomainService 出场的时候了,它避免了某些 Entity 承担过多与其无关的业务,导致过于臃肿。
Application 的职责类似于一个调度器,用来调度各个业务。但是它不应该体现具体的细节,业务的细节应该被封装在 Entity、ValueObject 以及 DomainService 中。
有几个小技巧可以判断是否体现了业务。
- 不要有 if / else 分支逻辑
- 不要有任何针对 Entity、ValueObject 上属性的读取和修改操作
概括的来说,Application层只做一件事:将输入的CQE(Command、Query、Event),通过调用领域对象进行业务处理,然后将结果组装成 DTO 返回。
/06 防腐层在那一层做?Application层?/
我觉得防腐层应该放在 Infrastructure 层,与 IRepository 的实现 Repository 同层,为了便于区分,你也可以将它单独一个包。
防腐层中的各种 Facade 应该与 Repository 有共同的 IRepository 。因为防腐层背后具体要做的事情就是从其它系统获取数据,这个职责和 Repository 是一致的。Repository 屏蔽了操作数据库的细节,而防腐层屏蔽了对接其它系统的细节。
我们这次就聊这么多。以上 6 个问题都是有多个人同时提到过的问题,我想它们也更具普遍性。
最后我们总结一下。
- DDD 必须要配合微服务一起用?不是,微服务只是 DDD 思想的一种实现方式。
- 限界上下文只能通过拆分成独立的 Service 体现?不是,单体应用中通过「模块」也可以。
- 业务简单的项目不适合 DDD ?不是,DDD 这个方法论对任何项目都有帮助。
- Entity 的属性字段需要对应数据表吗?无法做到一一对应,因为 Entity 可能存在嵌套。
- Application、DomainService 的区别?Application不包含业务,仅对业务进行调度。DomainService 实现不属于任何一个 Entity 的方法。
- 防腐层在那一层做?Application 层?Infrastructure 层,与 IRepository 的实现 Repository 同层。
好了,希望对你有所帮助。
最后再讲个题外话,「六边形架构」和「洋葱架构」在这些年伴随着 DDD 的概念被普及。其实所谓的六边形架构,他们的边就是由 DDD 概念中的防腐层构建的。所谓的洋葱架构,就是在六边形架构的基础上,针对内部业务对象进行建模,而 DDD 就是一种建模思想。
原创文章,转载请注明本文链接: https://zacharyfan.com/archives/1503.html
关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描二维码~
定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。
如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。
如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。