作者 | 张逸
最近重读Eric Evans的经典《领域驱动设计》,正如Eric提倡我们要去发现隐式概念一般,这次重读也让我发现了许多隐藏的DDD知识。恰好今日有朋友咨询我一些DDD问题,好似激活了触发器,随着问题的解答,我倒是在回答过程中又把这些知识梳理了一遍,才有了这篇杂记。
怎么看待DDD中的Repository?我们必须把握一个根本的底线,就是采用DDD方式设计Repository时,一定要忘记所有与数据访问有关的技术实现细节。Repository接口属于领域层,一旦我们将Repository视为DAO对象,就会不期然地重回数据驱动设计的老路。
Eric在书中写道:“Repository将某种类型的所有对象表示为一个概念集合(通常是模拟的)”。这句话一语道破天机,也是DDD得名的由来,必须是通过领域去驱动设计,也就是说在这个设计过程中,应尽量去掉技术的色彩。
借用Martin Fowler对重构的隐喻,在领域驱动设计过程中,也有两顶帽子:领域设计与技术实现。在进行领域设计时,考虑的应该是领域逻辑、业务规则,以及随之需要设计演进的领域模型;一旦开始关注技术实现,就应该切换到与领域完全无关的技术关注点上。这也就是我认为非常关键的点:分离技术复杂度和业务复杂度。
Repository是一个概念集合,我们在领域设计时,又需要保证领域概念的完整性,并考虑领域逻辑的不变性约束,因此,DDD才会引入Aggregate。同时,DDD明确约定:一个Aggregate只能有一个Repository,即聚合根的Repository。所有对聚合的访问都应该通过Repository来完成。
在《领域驱动设计》的第四章”分离领域“,Eric给出了几点DDD的适用范围:
因此,领域驱动设计绝对不是银弹,我们也不要将领域驱动设计视为拯救项目的灵丹妙药。从上述几点描述,我们似乎可以得出DDD的基础要素:
当我们开始做一个新项目时,有可能从一开始业务并没有多复杂,系统规模也不够大,没有运用DDD是可以接受的选择。但随着需求的增加与变化,项目规模与领域复杂度都达到了DDD的要求。这时该如何应对?
针对这种已有的系统,若要从Non-DDD形式演化为DDD形式,无非是两种策略:
倘若开启的新项目在领域复杂度上达不到DDD的要求,我仍然建议运用DDD,只不过需要将DDD的设计重点放在战略设计阶段,即对项目划分合理的Bounded Context。一旦确定了这些Context的边界,在边界之内进入战术设计阶段时,就可以不采纳DDD的设计方式,例如选择使用Transaction Script。
领域驱动设计的战略设计可以帮助我们识别微服务的边界。针对微服务内部,可以采用DDD的方式,也可以采用其他方式,这个并没有特别约束。
大体可以这样认为:
实践中,我们通常会使用DDD的Bounded Context、Context Map以及六边形架构来指导微服务设计。反过来,由于微服务强调服务的独立部署,因此微服务的引入重新定义了Bounded Context的边界,服务之间的通信也突破了Context Map的集成模式。
至于微服务对数据存储的设计约束——“每个微服务的数据单独存储”,属于基础设施层面,严格来讲,与领域驱动设计是没有任何关系的。