领域驱动设计(Thoughtworks洞见)
上QQ阅读APP看书,第一时间看更新

分层架构

在DDD方法提出后的数年里,分层架构的具体实现也经历了几代演进,直到Martin Fowler提炼出下图的分层实现架构后,才逐步为大家所认可。DDD的方法也得到了有效的补充,模型落地的问题也变得更容易,核心领域模型的范围也做出了比较明确的定义:包括了Domain,Service Layer和Repositories。

(Martin Fowler总结提出的分层架构实现,注意“Resources”是基于RESTful架构的抽象,我们也可以理解为更通用的针对外界的接口Interface。而HTTP Client主要是针对互联网的通信协议,Gateways实际才是交换过程中组装信息的逻辑所在。)

我们的核心实体(Entity)和值对象(Value Object)应该在Domain层,定义的领域服务(Domain Service)在Service Layer,而针对实体和值对象的存储和查询逻辑都应该在Repositories层。值得注意的是,不要把Entity的属性和行为分离到Domain和Service两层中去实现,即所谓的贫血模型,事实证明这样的实现方式会造成很大的维护问题。DDD战术建模中的元模型定义不应该在实现过程中被改变,作为元模型中元素之一的实体本身就应该包含针对自身的行为定义。

基于这个模型,下面我们来谈谈更具体的代码结构。对于这个分层架构还有疑惑的读者可以精读一下Martin的原文。有意思的一点是,这个模型的叙述实际是在微服务架构的测试文章中,其中深意值得大家体会。

这里需要明确的是,我们谈论代码结构的时候,针对的是一个经过DDD建模后的子问题域(参见战略设计篇),这是我们明确的组件化边界。是否进一步组件化,比如按照限界上下文(Bounded Context)模块化,或采用微服务架构服务化,核心实体都是进一步可能采用的组件化方法。从抽象层面讲,老马提炼的分层架构适用于面向业务的服务化架构,所以如果要进一步组件化也是可以按照这个代码结构来完成的。

总体的代码目录结构如下:

这个目录结构一一对应了前文的分层架构图。完整的案例代码请从GitHub下载。

可以看到实际上我们并没有建立外部存储(Data Mappers/ORM)和对外通信(HTTP Client)的目录。从领域模型和应用的角度,这两者都是我们不必关心的,能够验证整个领域模型的输入和输出就足够了。至于什么样的外部存储和外部通信机制是可以被“注入”的。这样的隔离是实现可独立部署服务的基础,也是我们能够测试领域模型实现的要求。