我正在参加「启航计划」

大家好,我是飘渺。今日持续更新DDD&微服务的系列文章。

在专栏开篇提到过DDD(Domain-Driven Design,范畴驱动规划)学习起来较为杂乱,一方面因为其本身涉及的概念颇多,另一方面,咱们往往缺乏实战经验和清晰的代码模型指导。今日,咱们将专注于DDD的分层架构和实体模型,期望为大家落地DDD供给一些有益的参考。首要,让咱们回顾一下了解的MVC三层架构

1. MVC 架构

在传统运用程序中,咱们一般选用经典的MVC(Model-View-Controller)架构进行开发,它将整体的体系分成了 Model(模型),View(视图)和 Controller(操控器)三个层次,也便是将用户视图和事务处理阻隔开,而且经过操控器连接起来,很好地完结了表现和逻辑的解耦,是一种标准的软件分层架构。

在遵从此分层架构的开发过程中,咱们一般会树立三个Maven Module:Controller、Service 和 Dao,它们分别对应表现层、逻辑层和数据拜访层,如下图所示:

新项目,不妨采用这种架构分层,很优雅!

(图中多画了一个Model层是因为 Model 一般只是简略的 Java Bean,只包含数据库表对应的特点。有的运用会将其独自抽取出来作为一个Maven Module,但实践上它能够合并到 DAO 层。)

1.1 MVC架构模型的不足

在事务逻辑较为简略的运用中,MVC三层架构是一种简练高效的开发形式。但是,跟着事务逻辑的杂乱性添加和代码量的添加,MVC架构或许会显得绰绰有余。其首要的不足能够总结如下:

  • Service层职责过重:在MVC架构中,Service层常常被赋予处理杂乱事务逻辑的使命。跟着事务逻辑的增长,Service层或许变得臃肿和杂乱。事务逻辑有或许分散在各个Service类中,使得事务逻辑的组织和保护成为一项挑战。
  • 过于重视数据库而忽视范畴建模:尽管MVC的规划初衷是对数据、用户界面和操控逻辑进行别离,但它在面临杂乱事务场景时并未给予范畴建模足够的重视。这或许导致代码难以了解和扩展,因为代码更像是围绕数据库而不是事务需求进行规划。
  • 鸿沟区分不清晰:在MVC架构中,顶层规划上的鸿沟区分并没有清晰的规矩,往往依靠于技能负责人的经验。在大规划的团队协作中,这或许导致职责不明晰、分工不清晰等问题。
  • 单元测验困难:在MVC架构中,Service层一般以事务脚本的方式进行开发,而且往往耦合了各种中间件操作,如数据库、缓存、音讯行列等。这种耦合使得单元测验变得困难,因为要在没有这些中间件的情况下运转测验或许需求大量的模拟或存根代码。

在深入探讨MVC架构之后,咱们将进入今日的主题:DDD的分层架构模型。

2. DDD的架构模型

在DDD中,一般将运用程序分为四个层次,分别为用户接口层(Interface Layer)运用层(Application Layer)范畴层(Domain Layer)根底设施层(Infrastructure Layer),每个层次承当着各自的职责和作用。分层模型如下图所示:

新项目,不妨采用这种架构分层,很优雅!

  1. 接口层(Interface Layer):负责处理与外部体系的交互,包含UI、Web API、RPC接口等。它会接收用户或外部体系的恳求,然后调用运用层的服务来处理这些恳求,最终将处理结果回来给用户或外部体系。
  2. 运用层(Application Layer):承当协调范畴层和根底设施层的职责,完结具体的事务逻辑。它调用范畴层的范畴服务和根底设施层的根底服务,完结事务逻辑的完结。
  3. 范畴层(Domain Layer):该层包含了事务范畴的一切元素,如实体、值目标、范畴服务、聚合、工厂和范畴事件等。这一层的首要职责是完结事务范畴的中心逻辑。
  4. 根底设施层(Infrastructure Layer):首要供给通用的技能才能,如数据耐久化、缓存、音讯传输等根底设施服务。它可被其他三层调用,供给各种必要的技能服务。

在这四层中,调用联系一般是单向依靠的,即上层依靠基层,基层并不依靠上层。例如,接口层依靠运用层,运用层依靠范畴层,范畴层依靠根底设施层。但值得注意的是,尽管根底设施层在物理结构上或许坐落最底层,但在DDD的分层模型中,它坐落最外层,为内部各层供给技能服务。

新项目,不妨采用这种架构分层,很优雅!

2.1 依靠回转准则

依靠回转准则(Dependency Inversion Principle, DIP)是一种有效的规划准则,有助于减小模块间的耦合度,进步体系的扩展性和可保护性。依靠回转准则的中心思维是:高层模块不该直接依靠低层模块,它们都应该依靠笼统。笼统不该该依靠具体的完结,而具体的完结应当依靠于笼统。

在 DDD 的四层架构中,范畴层是中心,是事务的笼统化,不该直接依靠其他任何层。这意味着范畴层的事务目标应该与其他层(如根底设施层)解耦,而不是直接依靠于具体的数据库拜访技能、音讯行列技能等。但在实践运转时,范畴层的目标需求经过根底设施层来完结数据的耐久化、音讯的发送等。

为了处理这个问题,咱们能够运用依靠翻转准则。在范畴层,咱们定义一些接口(如仓储接口),用于声明范畴目标需求的服务,具体的完结则由根底设施层完结。在根底设施层,咱们完结这些接口,并将完结类注入到范畴层的目标中。这样,范畴层的目标就能够经过这些接口与根底设施层进行交互,而不需求直接依靠于根底设施层。

2.2 DDD四层架构的优势

在杂乱的事务场景下,选用DDD的四层架构模型能够有效地处理运用MVC架构或许呈现的问题:

  1. 职责别离:在DDD的规划中,咱们尝试将事务逻辑封装到范畴目标(如实体、值目标和范畴服务)中。这样能够下降运用层(原MVC中的Service层)的杂乱性,一起使得事务逻辑愈加集中和明晰,易于保护和扩展。
  2. 范畴建模:DDD的中心理念在于经过树立赋有内在的范畴模型来更真实地反映事务需求和事务规矩,然后进步代码的灵活性,使其更简略习惯事务的变化。
  3. 清晰的鸿沟区分:DDD经过鸿沟上下文(Bounded Context)的概念,对体系进行清晰的鸿沟区分。每个鸿沟上下文都有自己的范畴模型和事务逻辑,使得大规划团队协作愈加明晰、高效。
  4. 易于测验:因为事务逻辑封装在范畴目标中,咱们能够直接对这些范畴目标进行单元测验。一起,根底设施层(如数据库、缓存和音讯行列)被笼统为接口,咱们能够运用模拟目标(Mock Object)进行测验,避免了直接与真实中间件的交互,大大提升了测验的灵活性和便利性。

接下来看看如安在代码中遵从DDD的分层架构。

3. 如何完结DDD分层架构

为了遵从DDD的分层架构,在代码完结时有两种完结办法。

榜首种是在模块中经过包进行阻隔,即在模块中树立4个不同的代码包,分别对应范畴层(Domain Layer)、运用层(Application Layer)、根底设施层(Infrastructure Layer)和用户接口层(User Interface Layer)。这种办法的长处是结构简略,易于了解和保护。但缺陷是各层之间的依靠联系或许不行清晰,简略导致代码耦合。

新项目,不妨采用这种架构分层,很优雅!

第二种完结办法是树立4个不同的Maven Module层,每个Module分别对应范畴层、运用层、根底设施层和用户接口层。这种办法的长处是各层之间的依靠联系愈加清晰,有利于下降耦合度和进步代码的可重用性。一起,这种办法也有助于团队成员更好地了解和遵从DDD的分层架构。但是,这种办法或许会导致项目结构变得杂乱,添加了项目的保护本钱。

新项目,不妨采用这种架构分层,很优雅!

在实践项目中,能够根据项目规划、团队成员的了解程度以及项目需求来挑选适宜的完结办法。关于较小规划的项目,能够选用榜首种办法,经过包进行阻隔。而关于较大规划的项目,主张选用第二种办法,运用Maven Module层进行阻隔,以便更好地办理和保护代码。不管选用哪种办法,关键在于保证各层之间的职责清楚,遵从DDD的准则和最佳实践。

在DailyMart项目中,我开始计划选用榜首种办法,经过包进行阻隔。但是,在微信群中进行投票后,发现近90%的人挑选了第二种办法。作为一个倾听粉丝意见的博主,我决定采纳大家的主张。因而,DailyMart将选用Maven Module层阻隔的方式进行编码实践。

新项目,不妨采用这种架构分层,很优雅!

4. DDD中的数据模型

在DDD中,咱们选用特定的模型来映射和处理不同的范畴概念和职责,常见的有三种数据模型:实体目标(Entity)、数据目标(Data Object,DO)和数据传输目标(Data Transfer Object,DTO)。这些模型在DDD中有着清晰的角色和运用场景:

  • Entity(实体目标): 实体目标代表事务范畴中的中心概念,其字段和办法应与事务语言保持一致,与耐久化方式无关。这意味着实体和数据目标或许具有完全不同的字段命名、字段类型,甚至嵌套联系。实体的生命周期应仅存在于内存中,无需可序列化和可耐久化。
  • Data Object (DO、数据目标): DO或许是咱们在日常作业中最常见的数据模型。在DDD标准中,数据目标不能包含事务逻辑,而且坐落根底设施层,仅负责与数据库进行交互,一般与数据库的物理表一一对应。
  • DTO(数据传输目标): 数据传输目标首要用作接口层和运用层之间传递数据,例如CQRS形式中的命令(Command)、查询(Query)、事件(Event)以及恳求(Request)和响应(Response)。DTO的重要性在于它能够适配不同的事务场景需求的参数,然后避免事务目标变成庞大而杂乱的”万能”目标。

在DDD中,这三种数据目标在许多场景下需求相互转化,例如:

  1. Entity <-> DTO:在运用层回来数据时,需求将实体目标转化成DTO,这一般经过一个名为DTO Assembler的转化器来完结。

  2. Entity <-> DO:在根底设施层的Repository完结时,咱们需求将实体转化为DO以存储到数据库。同样地,查询数据时需求将DO转化回实体。这一般经过一个名为Data Converter的转化器来完结。

当然,不管是Entity转DTO,仍是Entity转DO,都会有必定的开销,不管是代码量仍是运转时的操作来看。手写转化代码简略犯错,而运用反射技能尽管能够削减代码量,但或许会导致显著的功能损耗。这里给用Java的同学推荐MapStruct这个库,MapStruct在编译时生成代码,只需经过接口定义和注解配置就能生成相应的代码。因为生成的代码是直接赋值,所以功能损耗能够忽略不计。

新项目,不妨采用这种架构分层,很优雅!

在SpringBoot老鸟系列中我推荐大家运用 Orika 进行目标转化,理由是只需求编写少量代码。但是在DDD中不同目标都有严厉的代码层级,而且一般会引进专门的Assembler和Converter转化器,已然代码量省不了,必定要挑选功能最高的组件。

各种转化器的功能对比:Performance of Java Mapping Frameworks | Baeldung

5. 小结

本篇文章具体介绍了DDD的分层架构,并具体解说了如安在项目代码中完结这种分层架构。一起,还具体DDD中三种常用的数据目标:数据目标(DO)、实体(Entity)和数据传输目标(DTO)。这三种数据目标的差异能够经过下图进行精粹总结:

新项目,不妨采用这种架构分层,很优雅!

至此,咱们现已深入解析了DDD中的中心概念。一起,咱们的DailyMart商城体系已完结一切的前期预备,现在现已预备好进入实践的编码阶段。在接下来的章节中,咱们将从完结注册流程开始,逐渐探索如安在实践项目中运用DDD。

DDD&微服务系列源码现已上传至GitHub,假如需求获取源码地址,请重视公号 java日知录 并回复关键字DDD即可。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。