转自:https://www.deathearth.com/1246.html
DDD的一大好处便是它不需要使用特定的结构,由于核心域在限界上下文中,所以我们可以在整个系统中使用多种风格的结构。有些结构保卫者领域模型,能够全局性地影响系统,而有些架构则满足了某些特定的需求。我们的目标是选择适合于自己的结构和结构模式。
在选择架构风格和结构模式时,应该将软件质量考虑在内。同时,避免滥用架构风格和架构模式也是重要的。质量驱动的架构选择是种风险驱动方式(我们采用的架构是用来减少失败风险的,而不是增加风险)
DDD可以使用分层架构、六边形架构、SOA架构、REST风格、CRQS架构、事件驱动架构、数据网织和基于网格的分布式计算等架构方式/风格。
一、分层架构模式
分层架构模式被人为是所有架构的鼻祖。支持N层架构系统,因此被广泛地应用于Web、企业级应用和桌面应用。这种架构中,我们将一个应用程序或者系统分为不同的层次。下图是一个典型的传统分层架构。
如上所示,核心域只是位于架构中的一层,上层为用户界面层、应用层。下层为基础设施层。
分层架构的一个重要原则是:每层只能与位于其下方的层发生耦合。分层架构也分为2种:
1. 严格分层架构:某层只能与直接位于其下方的层发生耦合;
2. 松散分层架构:允许任意上方层与任意下方层发生耦合。
许多系统都是基于松散分层架构的。观察者模式的情况是较低层可以和较高层发生耦合【较少】
上图中
– 用户界面层是应用层的直接客户。
– 应用服务位于应用层中,且和领域服务是不同的。因此领域逻辑也不应该出现在应用服务中。应用服务可以用于控制持久化事务和安全认证,或者向其他系统发送基于时间的消息通知。因雇佣服务本身并不处理业务逻辑,但它却是领域模型的直接客户。应用服务是很轻量的,主要用于协调对领域对象的操作,如数据聚合。如果应用服务变得复杂许多,这通常意味着领域逻辑已经渗透到应用服务中了,此时领域模型将变成贫血模型。
– 应用层应该是很薄的一层。
其他情况处理
针对资源库A(某个领域)实现需要基础设施层B提供的持久化机制的分层逻辑处理。如果B实现了A则违反了分层架构原则。
依赖倒置原则
通过改变不同层之间的依赖关系达到改进目的。
采用依赖倒置原则,使领域层和基础设施层都只依赖于由领域模型所定义的抽象接口。由于应用层是领域层的直接客户,它将依赖于领域层接口,并间接地访问资源库和由基础设施层提供的实现类。应用层可以采用不同的方式来获取这些实现,包括依赖注入、服务工厂和插件。
六边形架构
一种具有对称性特征的架构风格。该架构中存在两个区域,分别是外部和内部区域,外部区域中不同的客户都可以提交输入;内部系统用于获取持久化数据,并对程序输出进行存储(数据库)、转发(消息机制)。
每种类型的客户都有它自己的适配器,用于将客户输入转化为程序内部API所能理解的输入。六边形每条不同的边代表了不同种类型的端口,端口要么处理输入,要么处理输出。如下图中有3个客户请求均抵达相同的输入端口(适配器A\B\C),另一个客户请求了适配器D。可能前三个请求使用了HTTP协议,后一个用了AMQP协议。端口是一个非常灵活的概念,当客户请求到达时,都有相应的适配器对输入进行转化,然后端口将调用应用程序的某个操作或者向应用程序发送一个事件,控制权由此交给内部区域。
下图中右侧的适配器E\F\G可以用来访问之前存储的聚合实例,实现资源库可以用关系型数据库、文档数据库、分布式缓存或内存存储的。
这里介绍的是一种设计的思想。
面向服务架构(SOA)
有以下8种设计原则
1. 服务契约:通过契约文档,服务阐述自身的目的与功能
2. 松耦合:服务将依赖关系最小化。
3. 服务抽象:服务只发布契约,面向客户隐藏内部逻辑。
4. 服务重用性:一种服务可以被其他服务所重用
5. 服务自治性:服务自行控制环境与资源以保持独立性,有助于服务的一致性和可靠性。
6. 服务无状态性:服务负责消费方的状态管理,不能与自治性发生冲突。
7. 服务可发现性:可以通过服务元数据来查找服务和连接服务
8. 服务组合型:一种服务可以由其他服务组合而成,不用管其他服务的大小和复杂性如何。
将以上原则和六边形架构结合起来,示意图如下:(这里可以有不同理解,不强求)
REST架构风格
REST(representational state transfer):表现层状态转移,一种软件架构风格。不是标准,可用可不用
通过URL定位资源,HTTP描述操作。
如下:
##表示顾客22的订单11号
https://www.alipay.com/customers/22/orders/11
##表示新增一个顾客
POST https://www.alipay.com/customers
RESTful是指基于REST构建的API就是RESTful风格
命令和查询职责分离(CQRS)
从资源库中查询所有需要显示的数据是困难的,特别是需要显示来自不同聚合类型与实例的数据时。领域越复杂,这种困难程度越大。 通过CQRS可以将这种复杂的领域数据映射到界面中。
CQRS[ Cammand-Query Responsibility Segregaion]的规则
1. 如果一个方法修改了对象的状态,该方法便是一个命令(Command),它不应该返回数据。
2. 如果一个方法返回了数据,该方法便是一个查询,它不应该通过直接/间接的手段修改对象状态。
这样的话,领域模型将一分为二,由命令模型和查询模型分开进行存储。如下图:
数据库分为只读,和读写。并根据不同的需求划分方法的业务编写。大量查询可以用数据库连接池。
根据不同的业务情况去处理查询模型(读)和命令模型(写)
事件驱动架构(EDA, event-driven architecture)
是一种用于处理事件的生成、发现和处理等任务的软件架构。下图用户阐述如何将事件驱动架构用于多个六边形架构系统。(当然完全可以把六边形架构替换为分层架构或者其他架构)
一个系统的输出端口所发出的领域事件将被发送到另一个系统的输入端口,此后输入端口的事件订阅方将对事件进行处理。
管道和过滤器
例如shell命令中的最简单的管道、过滤器
## 在phone_numbers.txt文件职工统计含有电话区号‘303’的所有文本行的数量
cat phone_numbers.txt | grep 303 | wc -l
基于消息的管道和过滤器处理过程的基本特征如下:
1. 管道是消息通道:过滤器通过输入管道接收数据,通过输出管道发送数据。实际上,管道即是一个消息通道。
2. 端口连接过滤器和管道:过滤器通过端口连接到输入和输出管道。端口使六边形架构成为首选的架构。
3.过滤器即是处理器:过滤器可以对消息进行处理,而不见得一定要进行过滤。
4.分离处理器:每个过滤处理器都是一个分离的组件。
5.松耦合:每个过滤处理器都相对独立地参与处理过程,处理器组合可以通过配置完成。
6.可换性:根据用例需求,可以重新组织不同处理器的执行顺序,通过配置完成。
7.过滤器可以使用多个管道:消息过滤器可以从不同的管道中读取数据,是中并行处理过程。
并行使用同种类型的过滤器:对最繁忙和最慢的过滤器来说,我们可以并行地采用多个相同类型的过滤器来增加处理量。
以上原则用于事件驱动架构后逻辑如下:
在真实的DDD应用场景中,领域事件的名字将反应业务操作。上图中的第一步,所发出的事件表示某个限界上下文中某个聚合的行为输出。第2步到第4步可以发生在相同的限界上下文中,也可以发生在不同的上下文中,他们将接收上一步所发出的事件,处理之后再发布新的事件以通知下一步。同时,这3个步骤还可以创建和修改相应上下文中的聚合。这都是在管道和过滤器架构中处理领域事件的常见输出。
长时处理过程
设计长时处理过程的不同方法
1、把处理过程设计成为一个组合任务,使用一个执行组件对任务进行跟踪,并且对每一个步骤和任务的完成情况进行存储(持久化),然后再根据执行的结果决定是否启动下一个业务环节。该方案更像是责任链模式的灵活应用,缺点是一开始就需要把该事件 可能触发的子任务进行备案,并对其执行结果进行登记注册,显然,这种方式适合于业务场景比较固定的情况。
2、同第一种方案类似,同样把处理过程设计为一个聚合,聚合成为活动协作的中心,一个或者多个聚合的实例充当中心任务的组件并维护过程中产生的异常等。
3、第三种是设计一种无状态的事件跟踪器,该跟踪器每一个事件的消费者的事件获取与执行情况,并且把执行结果反馈给跟踪器,该跟踪器获取结果后对其进行存储,这样不断的累加事件在消费过程中不断增加的处理结果,这样可以跟踪到事件流转的全部状态,并且按照存储的事件结果,决定是否正常执行完其一个完整的业务,该方案适合于消费者动态的场合,每一次事件的发布,都可以保证到期监听者对事件的消费以及结果的持有,只有当所有事件的状态是正常的,才能确定这个业务是完整的。
在实际的领域中,一个长时处理过程的执行器将创建一个新的类似聚合的状态对象来跟踪事件的完成情况。该对象在处理过程开始时创建,将所有的领域事件共享一个唯一标识。
当并行处理的每个执行流运行完成后,执行器都会接收到响应的挖才能拿事件。然后,执行器根据事件中的过程标识获取到与该过程相对应的状态跟踪对象实例,再在这个对象实例中修改该执行流所对应的属性值。状态如下:
事件源(技术解决方案,也可以是一种持久化机制)
有时,我们的业务可能需要对发生在领域对象上的修改进行跟踪。此时的跟踪有不同的层次,而每个层次又对应有不同的方法。通常,业务人员可能只关心某些实体的创建时间、最后修改时间和谁做的修改等信息。这是一种非常简单的跟踪方式,而对领域模型中每次单独的改变,这种方式没有提供任何信息。
随着要求更多的变化跟踪,业务层也需要更多的元素据。业务层开始关心每一次的单独操作,甚至某个业务的执行时间。满足这种需求的变化跟踪工具有 CVS,GIT等。
将这种概念应用到单个实体上,然后用在单个聚合上,在用于每个聚合,便能体会到在对象层面上跟踪变化的好处,进而体会到变化跟踪对于整个系统的好处。整个层面上的变化跟踪便是事件源的核心。模式如下
这里的事件源,对于某个聚合上的每次命令操作,都有至少一个领域事件发布出去,该领域事件描述了操作的执行结果。每个领域事件都将保存到事件存储中。
数据网织和基于网格的分布式计算
随着软件系统越来越复杂,用户越来越多,需求也更加地”大数据”化,传统的数据库可能会成为系统的性能瓶颈。这种情况只能在计算能力上寻求解决。数据网织(Data Fabric)可以满足这种性能上的需求。
数据网织也成为网格计算(网格计算是分布式计算的一种,是一门计算机科学。它研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分,然后把这些部分分配给许多计算机进行处理,最后把这些计算结果综合起来得到最终结果。)
数据网织对领域模型提供了自然的支持。分布式缓存可以非常容易地对领域模型进行持久化,此时可以将它看成是一种聚合存储。在数据网织中,聚合就是缓存中的值部分,聚合的唯一标识则是标识键。如下示例
String key = product.productId().id();
byte[] value = Serializer.serialize(product);
## key是标识键,value是值。表明了数据网织的存储
region.put(key,value);
数据复制(多节点缓存数据)
解决缓存失败而造成系统状态丢失的种种可能性。
对于缓存冗余性的工作机制,这里有一个例子:其中一个节点作为主缓存,其他节点作为二级缓存。如果主缓存失效,其中一个二级缓存将会成为新的主缓存。当先前失效的主缓存恢复之后,新主缓存中的数据将被复制到恢复后的缓存中,此时该恢复后的缓存将变为二级缓存.(和zk的选举有点类似)