领域驱动DDD在签到场景落地案例之架构模式(二)

承接DDD概念初识第二篇,第一篇传送地址:领域驱动DDD在签到场景落地案例之概念初识(一)

本篇文章介绍微服务设计原则,以此为设计思想,然后列举DDD常见架构模式,不同架构方式对比,在工作中根据业务选择合适的架构模式

  • L型四层架构
  • 六边形架构
  • 整洁架构

微服务设计原则

微服务设计原则中如高内聚低耦合、复用、单一职责等原则在此就不赘述了,这里主要强调以下几条:

第一条:要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计

微服务设计首先应建立领域模型,确定逻辑和物理边界后,然后才进行微服务边界拆分,而不是一上来就定义数据库表结构,也不是界面需要什么,就去调整领域逻辑代码。
领域模型和领域服务应具有高度通用性,通过接口层和应用层屏蔽外部变化对业务逻辑的影响,保证核心业务功能的稳定性。

第二条:要边界清晰的微服务,而不是泥球小单体

微服务完成开发后其功能和代码也不是一成不变的。随着需求或设计变化,微服务内的代码也会分分合合。逻辑边界清晰的微服务,可快速实现微服务代码的拆分和组合。DDD 思想中的逻辑边界和分层设计也是为微服务各种可能的分分合合做准备的。
微服务内聚合与聚合之间的领域服务以及数据原则上禁止相互产生依赖。如有必要可通过上层的应用服务编排或者事件驱动机制实现聚合之间的解耦,以利于聚合之间的组合和拆分。

第三条:要职能清晰的分层,而不是什么都放的大箩筐

分层架构中各层职能定位清晰,且都只能与其下方的层发生依赖,也就是说只能从外层调用内层服务,内层服务通过封装、组合或编排对外逐层暴露,服务粒度由细到粗。
应用层负责服务的编排和组合,领域层负责领域业务逻辑的实现,基础层为各层提供资源服务。

第四条:要做自己能 hold 住的微服务,而不是过度拆分的微服务

微服务的过度拆分必然会带来软件维护成本的上升,如:集成成本、运维成本以及监控和定位问题的成本。企业转型过程中很难短时间内提升这些能力,如果项目团队不具备这些能力,将很难 hold 住这些过细的微服务。而如果我们在微服务设计之初就已经定义好了微服务内的逻辑边界,项目初期我们可以尽可能少的拆分出过细的微服务,随着技术的积累和时间的推移,当我们具有这些能力后,由于微服务内有清晰的逻辑边界,这时就可以随时根据需要轻松的拆分或组合出新的微服务。

DDD常见的架构模式

L型四层架构

领域驱动DDD在签到场景落地案例之架构模式(二)_第1张图片
DDD 四层架构从上往下分别是用户接口层,应用层,领域层和基础设施层:

  • User Interface 为用户接口层(或表示层),本层主要存放用户接口层代码。前端应用通过本层向应用服务获取展现所需的数据。本层主要用于处理用户发送的 Restful 请求和解析用户输入的配置文件等,并将信息传递给 Application 层。主要代码形态是数据组装以及 Facade 接口等
  • Application 为应用层,定义软件要完成的任务,这一层所负责的工作对业务来说意义重大,也是与其它系统的应用层进行交互的必要渠道。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作。它没有反映业务情况的状态,但是却可以具有另外一种状态,为用户或程序显示某个任务的进度
  • Domain 为领域层(或模型层),负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心,领域模型位于这一层
  • Infrastructure 层为基础实施层,向其他层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件,等等。基础设施层还能够通过架构框架来支持四个层次间的交互模式、三方软件包、配置和基础资源服务等
    这种分层架构属于松散分层架构,即某层可以与它的任意下方层发生耦合,而经典三层架构属于严格分层架构,严格分层只允许某层和它临近的下一层发生耦合。
    DDD 四层架构在经典三层架构的基础上做了一些改良:
  • 在用户界面层与业务逻辑层之间引入了新的一层,即应用层
  • 将业务逻辑层更名为领域层,更加的贴合了语义
  • 将数据访问层更名为基础设施层,则突破了之前数据库管理系统的限制,扩大了这个负责封装技术复杂度的基础层次的内涵
    领域驱动DDD在签到场景落地案例之架构模式(二)_第2张图片

分层架构的设计是为了“高内聚,低耦合”,在三层架构的基础上DDD设计的L型四层架构,基础设施层给展现层、应用层、领域层提供服务
项目结构结构如下
领域驱动DDD在签到场景落地案例之架构模式(二)_第3张图片
Interfaces(用户接口层):提供HTTP、RPC、页面渲染等接口,数据传输给应用层
Application(应用层):应用层是很薄的一层,理论上不应该有领域业务规则或逻辑,主要面向用例和流程相关的操作,实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。

应用服务是应用层的service包,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务通过 API 网关向前端发布。还有,应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等

通过应用服务对外暴露微服务的内部功能,这样就可以隐藏领域层核心业务逻辑的复杂性以及内部实现机制。应用层的主要服务形态有:应用服务、事件发布和订阅服务。

为了实现微服务内聚合之间的解耦,聚合之间的服务调用和数据交互应通过应用服务来完成。原则上我们应该禁止聚合之间的领域服务直接调用和聚合之间的数据表关联。

注意:不要将本该放在领域层的业务逻辑放到应用层中实现。因为庞大的应用层会使领域模型失焦,时间一长你的微服务就会演化为传统的三层架构,业务逻辑会变得混乱。
Domain(领域层&领域服务):领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。

实体采用充血模型,在实体类内部实现实体相关的所有业务逻辑,实现的形式是实体类中的方法。实体是微服务的原子业务逻辑单元。在设计时我们主要考虑实体自身的属性和业务行为,实现领域模型的核心基础能力。不必过多考虑外部操作和业务流程,这样才能保证领域模型的稳定性。
Infrastructure(基础层):通用技术能力,网关、缓存、数据库、相关配置等
用户接口层代码结构
领域驱动DDD在签到场景落地案例之架构模式(二)_第4张图片
assembler:实现 DTO 与领域对象之间的相互转换和数据交换。理论上 Assembler 总是与 DTO 一同被使用
dto:数据传输的载体,内部不存在任何业务逻辑,通过 DTO 把内部的领域对象与外界隔离。
facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。
应用层代码结构
领域驱动DDD在签到场景落地案例之架构模式(二)_第5张图片
event(事件):事件包含两个子目录,publish主要存放事件发布相关代码,subscribe主要存放事件订阅相关代码,比如订阅的mq,定时任务等
service(应用服务):这里的服务是应用服务。应用服务对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务
领域层代码结构
领域驱动DDD在签到场景落地案例之架构模式(二)_第6张图片
Aggregate(聚合):聚合代码包的根目录,实际项目中以实际业务属性的名称来命名。聚合定义了领域对象之间的关系和边界,实现领域模型的内聚。

Entity(实体):存放实体(含聚合根、实体和值对象)相关代码。同一实体所有相关的代码(含对同一实体类多个对象操作的方法,如对多个对象的 count 等)都放在一个实体类中。

Service(领域服务):存放对多个不同实体对象操作的领域服务代码。这部分代码以领域服务的形式存在,在设计时一个领域服务对应一个类。

Repository(仓储):存放聚合对应的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定一个原则:一个聚合对应一个仓储。

特别说明:按照 DDD 分层原则,仓储实现本应属于基础层代码,但为了微服务代码拆分和重组的便利性,我们把聚合的仓储实现代码放到了领域层对应的聚合代码包内。如果需求或者设计发生变化导致聚合需要拆分或重新组合时,我们可以聚合代码包为单位,轻松实现微服务聚合的拆分和组合。

基础层代码结构
领域驱动DDD在签到场景落地案例之架构模式(二)_第7张图片
Config:主要放配置相关代码,比如redis配置,数据库配置,bean配置等
Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,可为不同的资源类别建立不同的子目录
系统总目录
领域驱动DDD在签到场景落地案例之架构模式(二)_第8张图片

六边形架构

领域驱动DDD在签到场景落地案例之架构模式(二)_第9张图片
六边形每条不同的边代表了不同类型的端口,端口要么处理输入,要么处理输出。对于每种外界类型,都有一个适配器与之对应,外界通过应用层 API 与内部进行交互。上图中有 3 个客户请求均抵达相同的输入端口(适配器 A、B 和 C),另一个客户请求使用了适配器 D。假设前 3 个请求使用了 HTTP 协议(浏览器、REST 和 SOAP 等),而后一个请求使用了 AMQP 协议(比如 RabbitMQ)。端口并没有明确的定义,它是一个非常灵活的概念。无论采用哪种方式对端口进行划分,当客户请求到达时,都应该有相应的适配器对输入进行转化,然后端口将调用应用程序的某个操作或者向应用程序发送一个事件,控制权由此交给内部区域。

应用程序通过公共 API 接收客户请求,并调用领域模型来处理该请求。我们可以将 DDD 战术设计的建模元素 Repository 的实现看作是持久化适配器,该适配器用于访问先前存储的聚合实例或者保存新的聚合实例。正如图中的适配器 E、F 和 G 所展示的,我们可以通过不同的方式实现资源库,比如关系型数据库、基于文档的存储、分布式缓存或内存存储等。如果应用程序向外界发送领域事件消息,我们将使用适配器 H 进行处理,该适配器处理消息输出,而上面提到的处理 AMQP 消息的适配器则是处理消息输入的,因此应该使用不同的端口。

整洁架构(洋葱架构)

领域驱动DDD在签到场景落地案例之架构模式(二)_第10张图片
整洁架构是类似于一个内核模式的内外层架构,由内及外分为四层,包含的内容分别为:

  • 企业业务规则
  • 应用业务规则
  • 接口适配器
  • 框架与驱动器

注意“企业业务规则”与“应用业务规则”的区别,前者是纯粹领域逻辑的业务规则,后者则面向应用,需要串联支持领域逻辑正常流转的非业务功能,通常为一些横切关注点,比如日志、安全、事务等,从而保证实现整个应用流程。

仔细分析这一架构模型,我们会发现许多有用的特征:
层次越靠内的组件依赖的内容越少,处于核心的 Entities 没有任何依赖。 层次越靠内的组件与业务的关系越紧密,因而越不可能形成通用的框架。 Entities 层封装了企业业务规则,准确地讲,它应该是一个面向业务的领域模型
Use Cases 层是打通内部业务与外部资源的一个通道,因而提供了输出端口(Output Port)与输入端口(Input Port),但它对外的接口展现的其实是应用逻辑,或者说是一个用例
Gateways(发消息)、Controllers(收消息) 与 Presenters(将模型数据转化为视图要显示的数据)本质上都是适配器(Adapter),用于打通业务逻辑与外层的框架及驱动器,实现逻辑的适配以访问外部资源
系统最外层包括框架和驱动器,负责与适配器对接的外部资源,不属于限界上下文开发的范畴,但选择这些框架和驱动器,是属于设计决策要考虑的内容
Robert Martin 的整洁架构将领域模型放在整个系统的核心,这一方面体现了领域模型的重要性,另外一方面也说明了领域模型应该与具体的技术实现无关。领域模型就是业务逻辑的模型,它应该是完全纯粹的,无论你选择什么基础设施,按照整洁架构的思想都不应该去污染领域模型。换句话说,就是领域是最稳定的,基础设施应该依赖于领域,而不能让领域依赖于基础设施。

六边形架构和整洁架构在本质上类似,而整洁架构的端口更多一些:对于 N 变形,当 N 为 6 时就是六边形架构,而当 N 趋近无穷大时就变成了整洁架构。

三种架构对比说明

四层架构,六边形架构,整洁架构都考虑了前端需求的变与领域模型的不变,这三种架构的核心都是领域
领域驱动DDD在签到场景落地案例之架构模式(二)_第11张图片

开发中遇到的痛点

1、实际应用中人们喜欢把内存模型和数据库模型保持一致。三层架构的大部分问题都是从这里衍生出来的。数据库模型的粒度如果很小,那么大量的表连接很快就会让数据库跑不动了,比如left join等联表操作。如果数据库模型的粒度如果很大(这是大部分项目的选择),代码的质量(重用性、稳定性、扩展性)就很差。由于没有从业务的角度去仔细定义每一个对象,每个人会根据自己的需要建立各种QueryModel或ViewModel,慢慢地类会多到想哭。

2、还有一些三层开发人员最终患上了数据库痴迷症,他坚信程序就应该做个搬运工,其他的事情都应该交给数据库来完成,业务逻辑也应该写进存储过程里面去,接手的人根本弄不清楚当时系统是为哪些业务服务。

3、正所谓有人的地方就有江湖,有设计的地方也一定会有架构。如果你是一位软件行业的老鸟,你一定会有这样的经历:一个业务的初期,普通的 CRUD 就能满足,业务线也很短,系统设计也很完善,此时系统的一切都看起来很 nice,但随着迭代的不断演化,以及业务逻辑越来越复杂,我们的系统也越来越冗杂,模块彼此关联,甚至没有人能描述清楚每个细节。当新需求需要修改一个功能时,往往光回顾该功能涉及的流程就需要很长时间,更别提修改带来的不可预知的影响面。于是 RD 就加开关,小心翼翼地切流量上线,一有问题赶紧关闭开关。

4、研发、测试、产品、运营遇到一个共同的问题,大家在交流问题时对于某块业务涉及的领域叫法不尽相同,大家都有各自的一套理解方式,在这上面沟通问题浪费了太多时间,软件最大开发问题在于此,业务人员和技术人员需要某种翻译才能交流,业务人员的描述研发不能理解,研发的专业术语业务听的也是云里雾里,最后的沟通过程变成好似没有在一个频道上的电视台一样各自播放自己的节目。DDD将业务人员和技术人员放到同一个层面上,给双方建立相同的共识

总结

最后,DDD是一种设计思想,目的是把复杂业务通过领域建模变得简单,前期如果有条件的话可以邀请领域专家加入,领域专家是对该业务有长期积累沉淀,对业务的拆分、业务目标、业务发展有完整的预期。虽然领域建模过程会投入较多经历和运营、产品、开发团队、领域专家大家经过多轮会议把项目定位确定清楚,把领域拆分清楚,把业务名词做到理解一致。但是后期的业务变化在领域中沟通和开发会变的高效。

本章介绍到这里,下一章将会以签到场景为案例,把DDD领域涉及落地!

你可能感兴趣的:(架构设计,架构,微服务,java)