概念
领域模型是以限界上下文为边界的,集成限界上下文是跨上下文的,所以领域模型不是重点,本章更多是技术实现的指导。
如前面章节所述,一个限界上下文很多时候就是一个代码工程,对应着一个服务进程,那么集成限界上下文,就是对应着平常说的进程间通信。在分布式系统中,就是分布式系统通信了。在多用户系统,也为了高可用,基本分布式集群部署,是必需的考虑。所以书中论述,其实也是多从分布式系统方面考虑。
那分布式系统有什么需要注意的呢?
网络是不可靠的;总会存在时间延迟,有时甚至非常严重;带宽是有限的;不要假设网络是安全的;网络拓扑结构将发生变化;知识和政策在多个管理员之间传播;网络传输是有成本的;网络是异构的。
总之分布式系统必须借助网络,网络通信是具有不确定性的,所以远程接口调用、消息发送或数据同步等都要考虑容错问题,可以联想到分布式CAP原理:CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。对于分布式系统,网络问题无法规避,所以P是必须要满足的,在强调用户体验的情况下可用性A也是首要要考虑的,所以就牺牲一致性了,把强一致性变成最终一致性。
在聚合那一章中也提到,一个事务原则上只对一个聚合进行更新操作(一个事务可以做到强一致性),如果多个聚合的情况,那使用最终一致性的解决方案。那集成限界上下文呢,跨上下文的通信,更只能使用最终一致性了。所以在下面列出的集成限界上下文的技术方案以及长时处理过程,都是以最终一致性作为指导原则的。
集成限界上下文的方式
集成限界上下文有三种常用的方式:RPC,REST,消息机制
RPC
书中作者不太喜欢,但其实在一个企业内是更流行以及更高效的方式,调用方便,通信效率高,再结合分布式服务治理框架,在服务的可管理性、容错和错误定位都能很好的解决。目前流行的RPC框架有阿里开源的Dubbo和Facebook开源的thrift。对于作者担忧的共享内核的使用方式(就是引入服务提供方的接口定义jar包)带来的耦合问题,其实这是一种企业内部方便的使用方式,你也完成可以只制订契约使用发布语言的方式来使用RPC的,比如对thrift,你可以只公布接口的IDL就行。
但RPC的系列化协议是可选的一般比较复杂,所以这使得RPC经常只用于企业内的上下文集成方式,因为这种复杂性对外通信增加了成本也增加了安全性。
RESTFull
就是RESTFull http接口,好处是非常明显的,成熟的公开的广泛使用的协议。所以对企业外集成,一般使用REST。然后REST http一般是同步调用的,即调用后需要服务提供方马上返回结果的(书中说法是:服务的提供方必须直接参与),所以客户端不是完全自治的。虽然也可以使用一些技术手段来优先:比如增加代理层(nginx负载均衡其实就是方案),或使用回调方案,但其实还不如直接在客户端做一些容错措施。下面放一个书中的图,是一个REST集成方案的典型用例。注意这里的CollaboratorService、UserInRoleAdaptor和CollaboratorTranslator便组成了一个防腐层。那HttpClient呢?也是防腐层的一部分,它是Adapter的技术实现,不属于领域模型的概念。
消息
这是作者最推崇的方式,使用领域事件,结合消息机制,使得系统间很好的解耦,书中原话是“在使用消息进行集成时,任何一个系统都可以获得更高层次的自治性”。而下一句是“只要消息基础设施工作正常,即使其中一个交互系统不可用,消息依然可以得到发送和投递”,虽然业务系统的自治性提高了,但增加了对消息基础设施的依赖,而在工程实践中,其实这也是需要考虑的因素。
“在有可能的情况下,我们应该最小化不同限界上下文之间的信息复制,甚至彻底消除”,因为要保证两个上下文之间数据同步是很困难的,虽然我们的目标也只是最终一致性,但还是存在诸多挑战(这里说一下工作实践的一般做法:数据增加版本,增量同步+定期全量数据校正),所以建议的方案是:只同步实体或聚合的唯一标识。
长时处理过程
这是消息集成方式的典型用法,使用消息异步机制解耦多个上下文,因为是异步的,所以使用一个长时处理过程Process的技术来跟踪一个多步骤的异步任务,怎么跟踪呢?任务或过程有自己的Id(ProcessId),有自己的资源库(持久化)和状态机,需要实现超时跟踪器;然后各个上下文及各个聚合的操作实现幂等支持;然后靠着状态的异步检查和定期检查,然后重试,实现长时处理过程的最终成功。其实这也是一个分布式系统事务补偿和最终一致性的体现。
不过有一点想补充,在实践中,每个上下文自己处理增加高可用的措施,比如完善的异常机制和重试机制,然后整体处理过程失败的几率是很低的,然后处理任务又比较多,这种情况,如果每一个任务都注册了一个Process,每个任务都定时检查,整个系统的消耗还是太多了。所以有一种方案是:Write Ahead Log,即在每个处理环节都进行日志记录,然后各个环节处理最终失败会有错误报警,只有出现错误报警了,才有系统自动或人工启动的补偿重试程序,然后从日志中去恢复和重试一个任务。而大部分情况,任务是都可以成功结束的,不需额外处理。
问题
P446中提出一个问题:敏捷项目管理上下文发出ProductCreated事件,是否直接在协作上下文中监听该事件并创建专属的Forum和Discussion。书中给出的答案是不行,协作上下文是服务提供方而项目管理上下文是下游服务,让上游支持下游的业务事件(Product是项目管理的专有业务概念)是不合适的,因为有很多下游服务都需要这样的支持,那协作上下文就承担过多的职责了。
其实我想说从另一角度理解,虽然敏捷项目管理上下文本地消耗了ProductCreated事件,但也是转化为CreateExclusiveDiscussion事件发出去,也就是其实协作上下文也是消费敏捷项目管理上下文的事件的,只是消费的事件不一样而已,所以问题转化为应该是发布什么事件消息才合适。在领域事件那一章,我们知道领域事件也是领域模型的一种,所以领域事件应该是以上下文为边界的,不应该直接发给其他上下文的,而能发布出去的不应该是领域事件,而是契约,是发布语言的表达,就是应该按服务提供的接口契约去发消息,至于消息体内容什么,那就看需要,可能消息体内容就是跟ProductCreated事件携带的内容一样,但也应该换一个对象名称发布出去。