分层设计与领域设计融合架构设计

目录

传统分层架构存在的问题

在这里插入图片描述

传统的分层架构会在 Service 层、Manager 层实现具体的业务逻辑,使用 DO、DTO、BO、VO 等进行数据传输,数据和行为基本完全隔离。

  • Service 层:相对具体的业务逻辑服务层。
  • Manager 层:通用业务处理层,具有有如下特征:
    • 对第三方平台封装的层,预处理返回结果及转化异常信息。
    • 对 Service 层通用能力的下沉,如:缓存方案、中间件通用处理。
    • 与 DAO 层交互,对多个 DAO 的组合复用。

这样的分层架构存在一个问题:因为分层采用的是包的形式进行的层间隔离,所以需要每一位开发人员都理解并且自觉遵守以上规范,但是在实际工作中因为开发人员对 Service 层和 Manager 层的区别并不特别清楚,或难以完全遵守手册中的规范,就导致 Manager 层除了沉底一些通用能力以外和 Service 层并没有什么本质区别。在实际的业务代码中 Service 层和 Manager 层都充斥了大量的第三方依赖,对系统的稳定性有很大的影响。每依赖一个第三方服务都要各种异常问题,这些异常处理的代码往往会和业务代码混在一起,当这种代码多了以后会使代码的可读性非常差。随着业务迭代速度也很快, Service 层和 Manager 层代码量迅速膨胀,业务逻辑变得越来越复杂。

领域驱动设计

领域驱动设计的概念是 2004 年 Evic Evans 在他的著作《Domain-Driven Design : Tackling Complexity in the Heart of Software》(领域驱动设计:软件核心复杂性应对之道)中提出的。

领域驱动设计是一套方法论,指导我们将复杂问题进行拆分、拆分出各个子系统间的关联以及是如何运转的,帮助我们解决大型的复杂系统在落地中遇到的问题。简而言之,DDD 是一个分而治之的过程,是一系列分而治之的方法论

Evic Evans 在著作中将软件系统的设计分为 2 个部分:

  1. 战略设计:提出了域、子域、限界上下文等重要概念,指导我们如何拆分一个复杂的系统。
  2. 战术设计:提出了实体、值对象、领域服务、领域事件、聚合、工厂、资源库等重要概念,指导我们对于拆分出来的单个子系统如何进行落地,在落地过程中应该遵循哪些原则。

在这里插入图片描述

以电子商务系统举例,大型电子商务系统的拆分过程中我们可以把电商系统这个单体应用拆分成:订单子系统、库存子系统、物流子系统、搜索推荐子系统等等。

DDD 在战略层面上的域、子域、限界上下文的划分思想和微服务的划分不谋而合。微服务的划分是也是将一个大的问题拆分成若干个小的问题,每一个小的问题用一个或多个微服务来解决。

  • 域对应一个问题空间,也就是上例中的电商系统;
  • 子域是把域这个大的问题空间拆分成若干个小的更容易解决的问题空间,也就是单体应用向微服务演进过程中划分出来的各个子系统;
  • 限界上下文是解决方案空间,每个子域对应一个或多个解决方案空间。

而 DDD 的战术设计部分就是指导我们如何落地一个系统才可以使系统具备高可扩展性、高可读性。

系统最终都要以代码的形式落地,而落地的工作都是由普通的开发人员来完成,系统是否具备高可扩展性、高可读性直接影响了整个团队的效率。

总结一下,DDD 在软件开发中的主要起到如下作用:

  1. 帮助分析理解复杂业务领域问题,描述业务中涉及的实体及其相互之间的关系,是需求分析的产物,与问题域相关。
  2. 是需求分析人员与用户交流的有力工具,是彼此交流的语言。
  3. 分析如何满足系统功能性需求,指导项目后续的系统设计。

领域驱动设计思想

DDD 的革命性在于其是面向对象分析的方法论,它利用面向对象的特性(封装、多态)有效地化解复杂性,而传统 J2EE 或 SSH 框架等事务性编程模型只关心数据。这些数据对象除了简单的 setter/getter method 外,不包含任何业务逻辑,业务逻辑都是以过程式的代码写在 Service 层中。这种方式极易上手,但随着业务的发展,系统也很容易变得混乱复杂。

DDD 关心的是业务中的领域划分(战略设计)和领域建模(战术设计),其开发过程不再以数据模型(Data Model)为起点,而是以领域模型为出发点,研发过程如下图所示。

在这里插入图片描述

领域模型对应的是业务实体,在程序中主要表现为类、聚合根和值对象,它更加关注业务语义的显性化表达,而不是数据的存储和数据之间的关系。

领域驱动设计面临的问题

DDD 尚存在一些争议,当前的核心问题是针对软件开发导致软件结构越来越混乱,越来越复杂,越来越难以维护的问题,开出的药方超越了当前众多公司的发展阶段和实操水平。

换句话说,当前大多数互联网企业软件开发经验尚且薄浅,缺乏积累和人才积累,盲目运用 DDD 技术开发,会极大增加开发成本。DDD 对开发者素质要求非常高,甚至达到了专家级要求,主要反映在三个方面:

  1. DDD 的设计和开发成本非常高。首先需要雇佣领域专家和掌握 DDD 设计与开发方法的开发者,在开发之前必须前置领域设计流程,在项目建设工期非常紧张的情况下,DDD 将导致开发效率降低,开发难度增大,开发工作量剧增的问题,不利于项目建设过程中的贯彻和坚持;
  2. DDD 缺乏成熟的 IT 大厂实践、分享和技术支持。目前社区资源不足,开源工程,例如:github/micro-company、github/ddd-leaven-akka-v2 和大型实践项目比较少。了解和掌握 DDD 的软件工程师凤毛麟角、不同公司业务场景各不相同、真正理解垂直市场的领域专家和全面掌握 DDD 设计的专家就更少了;
  3. DDD 的思想范式相对 CRUD、MVC 过于复杂,概念繁多,过于抽象,表述不够清晰,难以理解,不易被初级开发者掌握。DDD 的范式流派目前主要有 EDA、CQRS、Event Sourcing、In-Memory、DCI、CSP 等流派,它们之间互有冲突,不分伯仲,各自从某一个角度出发,解决部分问题,但也引出了其它新问题,例如:数据一致性危机,事务危机和代码复杂度问题,在当前绝大多数公司和业务场景下,必须混合使用,目前尚未出现大一统的理论架构。当前仍缺乏成熟的配套技术以降低开发者学习和运用其的难度和成本。主要体现在缺乏类似 Spring 这种现象级别的成熟框架,允许一个在校生学习一个月就基本可以进行生产级的代码开发工作。当前尚且不够成熟的 DDD 框架 AxonFramework、AKKA 或国产 JdonFramework 等学习成本比较高,不易被缺乏开发经验的工程师快速掌握应用。

分层设计与领域设计的融合

分层设计(Layered Architecture)跟DDD没有必然的联系

  • 用户界面层:负责向用户显示和解释用户指令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人(比如外部应用调用对应接口)。

  • 网关层:负责提供对外的 HTTP 服务或者其他应用层协议服务。

  • 应用服务层:定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使他们互相协作。它没有反应业务情况的状态,但是却可以具有另外一种状态,为用户或者程序显示某个任务的进度。

  • 领域服务层:负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反应业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心。

  • 基础设施层:为上面各层提供通用的技术能力,为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件(PS:这个在互联网应用中几乎用不到)等等。互联网 Web 应用系统中基础设施包含了数据持久化服务,中间件服务(数据库,Redis,Memcached,ZooKeeper,ELK等等)以及第三方服务等。

各层除了实现自己的功能外,还需要遵守以下原则:

  1. 各层除了实现自己的功能外,还需要遵守以下原则:
  2. 下层向上层发起的通信只能通过中间件等间接方式进行。
  3. 上层和下层只能有松散耦合(各自为独立个体,通过简单引用关联)。在某些微服务框架比如 Dubbo 中,可以把 API 包提供给上层引用即可。而 Spring Cloud 的上下层耦合更为松散,通过契约约定即可。前者的优点是调用者可以直接使用提供方定义好的契约和方法。后者的优点则在于最大限度的降低了耦合,避免在上层无限制的进行下层包引入。

应用服务层和领域服务层

这里重点说明应用服务层和领域服务层之间的关系。举一个例子:有一家上市企业A公司,靠卖水果发家,其首席架构师科学合理的按照 DDD 搭建了一套基于微服务体系的卖水果应用,其架构图如下:

在这里插入图片描述

今年水果行情一般,而房地产十分火热,A 公司高层发现房地产带动的五金行业也十分火热,于是下达任务给技术部,要求其立即着手搭建五金销售系统,货源已经谈好。得益于首席架构师之前优秀的架构设计,他发现只需要做一个卖五金的网站以及另外对微服务进行微量的调整即可满足老板的需求 —— 因为卖五金和卖水果并无本质区别,他们涉及的环节几乎一致。加入五金售卖的系统架构图如下:

在这里插入图片描述

可见:

  • 应用服务层:代表是某一个业务应用,它代表的更多的是从需求出发的应用定义。
  • 领域服务层:则是业务领域按照自身的边界进行设计的一个高内聚的服务体。

应用层通过协调和组合各个领域服务即可形成一个新的应用服务。《领域驱动设计》中明确指出,在设计领域服务时无需考虑表示层和持久层服务的东西。

领域划分和微服务化

根据 DDD 理论,领域建模主要发生在领域服务层,各领域模块都应该是高内聚低耦合的,具有清晰的业务边界。如何切分领域模块并没有一个明确的规则,不同的场景下可能相同的业务块边界也不尽相同。

  • 领域设计一定要有清晰的功能边界。一个领域服务对应了一个功能集合,这些功能一定是有一些共性的。比如,订单服务,那么创建订单、修改订单、查询订单列表,一般是订单域的功能集合。

  • 领域拆分并不是一步到位的,应当根据实际情况逐步展开。从单体应用到微服务体系的拆分过程能很好的说明这个问题,一上来拆的很细的改造方案一定会死的很惨。所以如果一开始不知道应该划分多细,完全可以先粗粒度划分,然后随着需要,初步拆分。比如一个电商一开始索性可以拆分为商品服务和交易服务,一个负责展示商品,一个负责购买支付。随后随着交易服务越来越复杂,就可以逐步的拆分成订单服务和支付服务。

  • 领域拆分并不是一成不变的,应当具体情况具体分析。

  • 领域可以是多个子领域的一个虚拟集合,换句话说多个微服务也可以形成一个大域,不必纠结于领域和微服务之间的数量对应关系。我们在做架构设计 PPT 的时候可能就把订单域作为一个领域,代表了这个域就是关于订单的,具体该有几个微服务,这需要更细的详细设计来提供。

  • 领域层服务设计应当是调用者无关的。这一点有点像第一点,但是它强调的是领域层服务的设计不应该受调用者的影响,这个观点在《领域驱动设计:软件核心复杂性应对之道》这本书里也可以找得到。领域层服务开发和设计的理念是关注自己的域,一旦边界划分清楚了,开发所需要考虑的永远都只是输入和输出,提供的服务一定是尽可能通用的,面向功能来开发的,而不是面向调用方来开发的。比如某个调用方提出了一个需求:调用方 B 希望 A 服务提供一个买汽车的接口,那么 A 服务设计的接口就应该是 buyCar(),而不是 buyCarForA()。

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/108590506