简介

基于领域驱动设计(DDD)开发的项目,它具有代码层次分明,业务更好的贴合业务,使代码的可维护性和可读性大大提高等优点,而MAVEN和Spring天生就是为模块化而设计开发的,最近结合之前的DDD使用经验,结合使用MAVEN和Spring对项目的模块化进行一次实践。
代码地址:https://github.com/liushprofessor/ddd_module

为什么要模块化和为什么需要微服务

之前本人参与过的项目中有些采用了SpringCloud全家桶对项目进行微服务化,但是经过一番实践后发现大多数项目并不需要进行微服务化,大都数使用微服务只是为了强制使各个服务的代码隔离,甚至很多情况下所有的服务的数据库还在单个数据库中存储,当我问他们为什么使用单体应用时他们常说为了让各个服务的代码隔离,日后好维护。但是在日常开发中这种四不像赶鸭子上架的开发行为并没有使代码变得更简洁,而且采用微服务在配套设施(如全链路追踪,系统异常检测等)没有发展起来的时候,对线上的问题排查,应用的部署和维护简直是噩梦。所以解决代码业务隔离的问题并不只有微服务这种方法,我们使用maven和Spring就能很好的处理代码业务隔离性的问题,而且使用模块化避免了日后重复功能的开发,比如权限模块,如果之前已经将权限功能进行模块化后,那么后续就不需要在开发,只需要使用MAVEN将需要的模块组装起来即可,个人认为也可类比为中台。

为什么要使用领域驱动设计(DDD)

简单的来说代码的核心是你的业务,所以代码能很好的体现业务,并且将业务代码剥离出来那么代码的可读性和维护性就会更好,举个例子,代码写完后一个月你不去动它,那么你就会忘记,但是业务却没那么容易忘记,而且业务往往有详细的设计和使用文档,如果我们的代码很很好的映射成我们的业务,那么时候意味着日后的扩展会更好?关于领域驱动设计的基础概念可查看之前的文章。

系统分层

在这里插入图片描述

本次实践采用的项目结构依旧是六边形架构,此架构凸显了越是核心的代码就要放在项目的最底层,这样才能避免核心代码免受其它非核心代码的干扰。简单的举例来说,对于项目而言,最核心的代码应该是项目逻辑代码,这部分代码不应该受到数据库持久层等代码的干扰(持久层代码的变化不应该影响业务代码),这里举个例子,大家编写代码的时候都依赖过其它项目的包,你需要调用依赖包的方法,经常会出现你依赖的包版本升级导致了你的代码编译报错,为什么会出现这种情况?这是因为你依赖了它的包,那么带来的问题就是你依赖的包发生了变化,那么你的代码就有可能出问题,但是相对你而言,你的代码才是业务的核心,你只是调用了第三方的方法去实现了一个功能,这里相对你来说业务代码才是最重要且在项目中最稳定的一部分,他不应该受到其它层次代码的干扰,如何解决这个问题?其实Spring已经给了我们答案。

依赖倒置和依赖注入

在刚刚接触到Spring时常常听人说Spring的核心理念就是依赖导致和依赖注入,至于什么事依赖倒置什么是依赖注入,即为什么要使用依赖倒置和依赖注入在接触到领域驱动设计之前其实也是一知半解,这里结合我的实际经验和体会谈谈为什么我们需要Spring需要依赖倒置和依赖注入。

  • 依赖倒置:依赖倒置我们可以首先根据直面上的意思去理解,就是依赖的关系发生倒置,举个大家都理解的例子,你的领域层(如果不了解领域驱动设计可以将其理解为service)需要将数据持久化到数据库,那么你就会调用DAO去持久化,那么按照领域驱动的设计来说,你的领域层代码才是最核心最稳定的内容,他不应该受到DAO的影响,假设我这里将之前service调用的一个DAO的方法删除,那么service就报错了,又由于我们系统的核心是领域层,它应该是最稳定的代码不应该受到其它非核心代码的干扰。好的做法是将依赖倒置,让我们的DAO去依赖领域层(Service),这样即使DAO再怎么变化也不会影响到领域层(Service),为了实现这个目的我们在领域层(Service)上开了一个java接口,由DAO层去实现这个接口,那么现在代码就变成了DAO依赖领域层(Service),如果你用过maven现在的依赖关系就变成了DAO的pom文件中依赖了领域层(Service),因为DAO需要实现到领域层的接口,那么在领域层中需要使用到DAO的实现时只需要调用自己开放给DAO的接口即可。
  • 依赖注入:仅仅使用依赖倒置是不足以使依赖发生倒置的,因为虽然现在领域层使用DAO的实现时调用了自己开放给DAO的接口,但是接口需要实现,如何让接口知道现在使用的是哪个实现类呢?如果在领域层去告诉代码DAO的接口具体是由哪个类实现的,那么这里又会出现了循环依赖的问题,即DAO依赖领域层(DAO实现了领域层的接口),领域层也依赖了DAO(需要在领域层中配置接口是由哪个DAO的实现类实现的),这样会导致更加严重的循环依赖问题。那么如何解决这个问题呢?答案就是我再引入一个通用的模块,所有层去依赖这个模块,你把你DAO需要实现的对象注入到Spring中,同样领域层需要渠道DAO的实现时去Spring中去获取,那么现在领域层不再依赖DAO层,而是依赖了更加通用稳定的的中间层Spring,这样就处理了系统层次划分的问题。
项目结构的划分

以下是项目结构的划分,以下是使用maven和Spring进行模块化进行的实践,其中app负责组装各个模块,auth是我创建的一个模块,模拟用户角色系统,开发完各个模块后,我们把auth创建层springboot starter,以下是我为何这样分层,和一些个人的的见解:

  • 关于文档

    文档我采用Spring Rest doc,因为个人不喜欢Swagger的侵入的注解。

  • 关于持久层框架

    持久层我没有采用mybatis plus 因为我不喜欢它的类的命名方式不够直观,感觉有违领域驱动的理念,我更喜欢类Spring data

  • 关于依赖倒置

    上文说到,我创建了领域层domain,和基础设施层infrastructure,持久层就是在这个包下实现的,根据上文所说,领域层是最核心的层,需要放在依赖链的最下层,但是domain又需要调用持久层中的代码去持久化数据,所以我在domain下写了一个接口,由基础设施层infrastructure去实现,而我又使用Spring去管理对象的什么周期,使用依赖注入去注入对象从而调用infrastructure层的方法,这样达到依赖倒置的目的。

  • 关于各个层的反腐

    在我之前项目中我见过的大多项目没有明确的区分各个层次的对象,常常就是controller中传入一个DTO对象,然后这个DTO对象一直往下传,一直到DAO中进行持久化,这样会导致什么问题?这违反了领域驱动中领域层需要放在项目最底层的理念,因为领域层传入参数是controller中的DTO对象,导致了领域层依赖了controller层,而且最近在维护之前公司的一个老项目,且没有任何文档,在这种情况下走查代码,只能通过controller层一层层往下看,但是你又会发现DTO被各个层无限复用,入口传入的DTO中有许许多多不需要的对象属性,让我根本不无从下手。为了避免这种情况下,我讲controller,domain,repository层的对象严格区分,在这些包下都创建了pojo包或者dto的包,在进入每个层次前都需要进行防腐转换,不要让不相关的代码污染到其它的层次,这样导致多了很多对象加大了工作流,但是如果能让代码更加清晰多一些对象又何妨?所有涉及模式基本都是采用增加类和换区代码的灵活度。

  • 关于批量操作

    在领域驱动中所有command操作(insert,update)都需要走领域逻辑,但是批量查询为了兼顾效率等问题,我们可以直接从repository中读取对象然后直接返回回去。

  • 方法体现意图

    在领域驱动中,你需要将你的意图封装成方法,你的方法名需要体现出你需要执行意图。这样的好处就是当你查看方法时,一进入方法就知道你要执行什么东西,印证了那句话,代码是给人看的,只是顺便让机器执行以下而言

├── app
|  └── src
|     ├── main
|     |  ├── asciidoc
|     |  ├── java
|     |  |  └── com
|     |  |     └── liu
|     |  └── resources
|     └── test
|        └── java
|           └── com
|              └── liu
|                 └── auth
└── auth
   └── src
      ├── main
      |  ├── java
      |  |  └── com
      |  |     └── liu
      |  |        ├── common
      |  |        |  ├── pojo
      |  |        |  └── utils
      |  |        ├── controller
      |  |        |  └── dto
      |  |        ├── domain
      |  |        |  ├── client
      |  |        |  └── repository
      |  |        └── infrastructure
      |  |           ├── mapper
      |  |           ├── pojo
      |  |           └── repositoryImpl
      |  └── resources
      |     ├── com
      |     |  └── liu
      |     |     └── infrastructure
      |     |        └── mapper
      |     └── META-INF
      └── test
         └── java
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐