在 理解了 限界上下文 以及 分层架构 的本质基础上 需要确认系统的代码模型
每个团队 无需 都遵守一套 代码模型
在同一个项目中 必须
1遵守 同一个代码模型 并需要
2 知道 如此划分代码的 意义 与价值
代码模型设计
之前已经分析过
1 层与层之间的协作
2 跨限界上下文之间的协作
考虑限界上下文的代码模型时,需要考虑纵向架构除前端之外的所有层次或模块
在代码模型设计因素中,需要考虑
1 层与模块之间的职责分离与松散耦合
2 将整个限界上下文作为基本设计单元,照顾到限界上下文之间的协作关系
application: DDD 的应用层,对应限界上下文中 所有的应用服务
interfaces: 对 gateways 中除 persistence 之外的抽象 包括以下几方面:
访问除数据库之外其他外部资源的抽象接口(interface)
访问第三方服务或其他限界上下文服务的抽象接口
备注:从分层架构的角度讲,interfaces 应该属于应用层,但在实践时,往往会遭遇领域层需要访问这些抽象接口的情形,单独分离 出 interfaces,非常有必要。(领域依赖于其他服务的接口)
domain: 领域层 (纯业务逻辑层 不包含技术实现) 不依赖于repositories repositories体现的是它在基础设施层 扮演的与外部资源 打交道的语义
repositories:资源库,皆为抽象类型。如果该限界上下文的资源库并不复杂,可以将 repositories 合并到 domain 中。
gateways: 基础设施层 视外部资源的集成需求划分不同的包
controllers放在 gateways 之下,还是想体现它的网关本质
persistence 对应了 repositories 抽象,至于
其余网关,对应的则是 interfaces 下的抽象,如:消息队列以及与其他限界上下文交互的客户端
client 包下的实现类与 interfaces 下的对应接口组合起来,等同于上下文映射中“防腐层(ACL)”的概念
1 分层架构的层 最终需要映射到 模块 或者 包 上 无法通过语言严格界定各层 需要架构师 与 各个RD对齐
2 无论代码结构是否表达了层的概念,都需要充分理解分层的意义,并使得整个代码结构在架构上要吻合分层架构的理念。
3 每个模块或包都是单一职责的设计,在整个代码模型中扮演着不同的角色,有的对应了分层架构的层,有的代表了领域驱动设计的设计要素,有的则是为了保证架构的松散耦合。
不考虑 Repository 在领域驱动设计中的特殊性,而仅仅将其视为一种网关,可以将 repositories 融合进入interfaces 中
controllers 对应上下文映射的开放主机服务(OHS)模式,client 对应上下文映射的防腐层(ACL)模式 得到如下代码模型:
限界上下文的通信边界会直接影响到代码模型的设计决策。
order进程 调用 notification 进程
NotificationService 接口来隔离这种实现机制 NotificationClient 实现了 NotificationService接口 作为防腐层隔离了 notification的变化
notification需要提供开房主机服务(http接口 或 rpc接口)
限界上下文之间采用进程内通信,需要注意如何在代码模型中体现限界上下文的边界(分包)
考虑两个处于相同进程中的限界上下文彼此之间该如何协作,存在如下几种方式:
简单:在下游限界上下文的领域层直接实例化上游限界上下文的领域类。
解耦:在下游限界上下文的领域层通过上游限界上下文的接口和依赖注入进行调用。
迁移:在下游限界上下文中定义一个防腐层,而非直接调用。
清晰:要保证领域层代码的纯粹性,应该避免在当前限界上下文中依赖不属于自己的代码模型
迁移:在下游限界上下文中定义一个防腐层,而非直接调用。
NotificationClient 不再通过跨进程调用的方式发起对 RESTful 服务的调用,
防腐层的实现中NotificationClient 直接通过实例化的方式调用了 Notification Context 应用层的 NotificationAppService。这是在 Order Context 中,唯一与 Notification Context 产生了依赖的地方。
限界上下文采用进程内通信,也仅仅是封装在防腐层中发起调用的实现有所不同,即前面例子中的 NotificationClient,而这其实并不影响代码模型。
无论是进程间通信,还是进程内通信,我们设计的代码模型其实是一致的,并不受通信边界的影响 原因:
通信边界的划分是物理意义,代码模型的划分是逻辑意义,二者互相并不影响。
为保证系统从单体架构向微服务架构迁移,应保证代码结构不受架构风格变化的影响。
本书的域名为 practiceddd,对于一个电商系统,无论限界上下文的边界为进程间通信还是进程内通信,上下文的命名空间都应该为practiceddd.ecommerce.{contextname},
订单上下文 praticeddd.ecommerce.ordercontext
商品上下文的命名空间为 praticeddd.ecommerce.productcontext
代码结构如下所示:
两个或多个限界上下文还存在共同代码,只能说明一点:那就是我们之前识别的限界上下文有问题!
在第17课“上下文映射的团队协作模式”中,我们提到的“共享内核”模式就是用来解决此类问题的一种方法。
一旦提炼或发现了这个隐藏的限界上下文,就应该将它单列出来,与其他限界上下文享受相同的待遇,即处于代码模型的相同层次,然后再通过 interfaces 与 gateways/client 下的相关类配合完成限界上下文之间的协作即可。