… 接上
SOA 架构
面向服务架构(Service Oriented Architecture,SOA)对于不同的人来说意思不同。这里梳理一下SOA原则:
-
服务契约 : 通过契约文档,服务阐述自身的目的与功能。
-
松耦合 : 服务将依赖关系最小化
-
服务抽象 : 服务只发布契约,隐藏内部实现细节
-
服务重用性 : 一种服务可以被其他服务所重用
-
服务自治性 : 服务自行控制环境与资源以保持独立性
-
服务无状态、服务可发现、服务组合性
下图给出了支持SOA的六边形架构,包括SOAP、REST和消息服务。相比于微服务架构而言,SOA更关心重用;而微服务更重视迭代。
REST (Representational State Transfer)
近年来,REST可以被认为是最被滥用的架构语(其次就是微服务)。有人认为REST就是使用HTTP来直接发送XML的;有人认为REST就是以HTTP来发送JSON的;还有人则认为使用REST就是把URI查询参数传递给方法。以上这些解释都是错误的。关于REST的更深一步解读,读者可参阅本人的另一篇文章
HTTP api & RPC & REST & RESTful & GraphQL
这里简单说一下理解REST的要点:
-
REST是属于Web架构的一种架构风格
-
HATEOAS(Hypermedia As The Engine of Application State)是REST围绕的核心
-
单个资源并不独立存在,不同资源链接在一起才为Web。
在DDD结合REST中,一般我们不建议把领域模型直接暴露。因为这样会使系统结构变得脆弱,而且领域模型的变化很可能会导致接口的变化。
通常情况下,会在领域模型上再封装一层,通过资源抽象把系统功能暴露给外层。
CQRS 、事件驱动架构、微服务与DDD
DDD实践中经常会与读写分离(CQRS)结合事件溯源(Event Sourcing)一起从而对微服务架构可以起到非常好的辅助作用。这里,让我们仔细研究一下它们之间的联系。
首先,我们先简要看一下微服务架构的设计思路:
微服务架构的核心理念是把系统功能拆分到多个服务中,每个服务都是高度自治的,并且拥有独立的持久化层。各个服务之间是松耦合的。关于微服务的优点,诸如敏捷开发、持续迭代、弹性扩展等,笔者就不一一赘述了。微服务实现中,服务的编排治理通常是一个难点;DDD对于这一点却起到了辅助作用,因为DDD的子域划分天然就符合微服务的服务划分,也就是说每个子域的功能封装为一个微服务从业务上看是非常清晰和合理的,每一个微服务代表一方面的业务处理能力。
遗憾的是,事情没有这么简单。
最大的挑战是如果以微服务来实现业务系统,事务、领域模型、查询这三个都不太适合于微服务的功能拆分。让我们来详细分析一下,假设如下图的一个领域模型:
挑战 1:事务跨越服务问题
传统的单体应用程序可以依赖关系数据库的ACID特性来支持事务一致性。但这在已拆分的微服务架构中就行不通了,上图中Customer Service,Order Service 和 Catalog Service分别代表三个微服务,致使Order表和客户表存在不同的数据库中。跨数据库实现事务一致性最早的解决方案是两阶段提交(2PC),但由于这种方案的诸多缺点现在已经很少采用了。
在流行的解决方案中,根据CAP理论来权衡一致性、可用性和分区容忍性是一个不错的选择。稍后我们会看到事件驱动架构如何解决这个问题。
挑战 2 :查询跨越服务问题
客户端程序经常会请求查询和生成报表,在传统解决方案中会经常涉及表的连接。但在需要连接的表是在不同的数据库时问题就产生了。举例来说,例如某个查询需要连接客户表和订单表,但是客户表是在客户服务所维护的数据库中,而订单表是由另外的订单服务所维护的数据库中,显然我们有麻烦了。
挑战 3 :领域模型分解问题
上图的领域模型中分成了三个子域(子域在这里其实并不合适,应该是界限上下文Bounded Context或聚合。但由于这个概念还没有提到,所以这里我们暂时理解为子域)需要共同协作,相互存在引用关系。微服务的边界将会使这种引用变得困难。在本系列文章的总结部分,我们还会再次讨论这三个问题。
很明显,如果我们希望用微服务架构来支持业务应用,我们需要对上述三个问题给出解决方案。DDD中推荐的做法就是事件溯源(Event Sourcing)+读写分离(CQRS)。
- CQRS ( Command Query Responsibility Segregation )
CQRS将应用分成两个部分:
命令部分(command side),会改变业务对象的状态,例如HTTP PUT,POST,DELETE操作。
查询部分(query side),只会读取业务对象,例如HTTP GET。通常,我们会为支持查询部分构造物化视图,并且保证视图数据和命令部分所维护的数据是同步的。
Bertrand Meyer对CQRS模式有如下评述:
一个方法要么是执行某个动作的命令,要么是返回数据的查询,而不能两者皆是。换句话说,问题不应该对答案进行修正。更正式的解释是:一个方法只有在具有参考透明性(referentially transparent)时才能返回数据,此时该方法不会产生副作用。
CQRS带来了一些优点,例如我们可以分别优化和扩容命令部分和查询部分、两边的存储可以采用不同的技术栈以适应命令和查询模式的不同需求。对于查询所需的视图,我们可以根据应用所需做一些预连接,以文档数据的形式进行面向聚合的存储。
关于两边数据同步的问题,大体有两种方案。最常见的就是采用关系数据库主从模式,主数据库支持命令模式,而从数据库支持查询模式。这种方案就是常说的读写分离。
另外一种更灵活的方案就是以下面要介绍的事件溯源的方式来支持。
- Event Sourcing
事件溯源是事件驱动架构最常用的方案。事件驱动架构(Event-Driven Architecture, EDA)是一种用于处理事件的生成、发现和处理等任务的框架。
传统方案中在保存应用或业务对象的状态经常采用关系数据库或NoSQL。这种方案虽然简单直接,但是它并能够提供某个业务对象或者数据是如何达到当前状态的。举个例子,假设我们需要获得用户对某个论文的读操作的次数,数据库会存储最终值;但如果我们想知道某个论文每天被读了几次,这就非常困难了。
Event Sourcing会通过记录所有相关历史事件的方式来保存应用或业务对象状态。当前状态可以从历史事件中推衍出来,因为每一个事件代表了应用或业务对象状态的一次改变或一个事实。事件告诉我们应用所发生的所有真相。从某种程度来讲,事件提供了对业务对象和应用的审计日志。当前状态可以称之为物化状态(Materialized state)。
在这个方案中,会有专门的支持事件回放(Event Replay)等操作的事件存储(Event Store)。为了性能考虑,实践中经常会按照一定规则生成状态快照(State Snap),这样的话物化状态就可以通过最近的快照结合快照后发生的事件序列回访来得到。
- CQRS + Event Sourcing
上图中展示了CQRS+Event Sourcing的逻辑架构,其中数据的同步时通过事件的发布以消息队列承载而完成的。显然这种方案只能提供最终一致性。
下图展示了从DDD视角审视CQRS+Event Sourcing
到现在为止,我们还没有能够给出对上面所讲到的三个挑战的完整解决方案。因为该方案涉及DDD的一些基本概念,所以将会在后面讨论。
DDD 洋葱圈架构/整洁架构
让我们暂时把思路拉回到DDD的六边形架构,在其发展之下,又产生了另外两种架构:洋葱圈架构和整洁架构。
洋葱圈架构其实只是把六边形架构中的领域层分成了领域模型和领域服务;把应用分成了应用服务层和用户接口层而已。
整洁架构(Clean Architecture),正如其名,非常规范和整洁。这个架构试图从更广泛的企业视角、且采用更具有泛化能力的设计思路解决总体企业所有业务领域。最内层为实体,它封装了整个企业的业务规则集合和业务实体;然后是用户用例,它封装了具体应用的业务规则;再后是接口适配层,它封装了控制器、网关和展示器;最外面就类似于六边形架构的基础设施,不同之处在于用户接口被融入到了这里。
我们再从一张图上进一步理解一下这几种架构之间的关系:
关于架构方面的话题,我们暂时先聊到这里。有些问题还没有解决,在下一节介绍完领域驱动设计基本概念后,我们还会讨论诸如分布式事务、数据网格等架构相关问题。
未完,待续…
DDD(Domain Driven Design) 领域驱动设计从理论到实践 一
DDD(Domain Driven Design) 领域驱动设计从理论到实践 二
DDD(Domain Driven Design) 领域驱动设计从理论到实践 三
DDD(Domain Driven Design) 领域驱动设计从理论到实践 四
DDD(Domain Driven Design) 领域驱动设计从理论到实践 五
DDD(Domain Driven Design) 领域驱动设计从理论到实践 六
DDD(Domain Driven Design) 领域驱动设计从理论到实践 七
DDD(Domain Driven Design) 领域驱动设计从理论到实践 八
DDD(Domain Driven Design) 领域驱动设计从理论到实践 九