作者:小傅哥
博客:bugstack.cn

沉淀、共享、生长,让自己和他人都能有所收成!😄

本文来自于小傅哥新编写的 《Java简明教程》 系列内容,本教程意在于经过简略、明晰、明晰的成系统内容,教会Java学习伙伴,能够在学习后能进行Java项目开发。

今日要共享的是 MVC 和 DDD 的架构本质,经过由浅入深的介绍解说和视频带着手把手操作创立工程架构。让不管是学习 MVC 的小白码农仍是期望了解更多关于 DDD 内容的老白码农,都能够学习到一点自己需求的内容。

一、MVC 架构

假如咱们测验把编程的杂乱架构缩小到最简略了解的程度,那么编程开发其实只做3件事:”界说特色创立办法调用展示“。但由于同类所需的内容较多,如一系列的特色,一堆的办法完成,一组的接口封装,那么就需求合理的把这些内容分配到不同的层次中去完成,因而有了分层架构的规划。

那么本文小傅哥会向咱们介绍一套MVC架构的分层规划以及怎么创立运用,并供给相应的简略的案例。你能够仿制这套架构在自己的场景中运用,也更能便利编程的小白能够更快的上手开发。

注意:此套MVC架构模型合适供给HTTP服务的工程架构,合适简略的小场景开发运用。特色;简便、简略、学习本钱低。

1. 编程三步

假如说你是一个特别小的玩具项目,你乃至能够把编程的3步写到一个类里。但由于你做的是正经项目,你的各品种;方针类、库表类、办法类,就会三五成群的来。假如你想把这些三五成群的类的内容,都写到一个类里去,那么便是几万行的代码了。—— 当然你也能够吹嘘逼,你一个人做过一个项目,这项目大到啥程度呢。便是有一个类里有上万行代码。

所以,为了不至于让一个类撑到爆💥,需求把黄色的方针、绿色的办法、赤色的接口,都分配到不同的包结构下。这便是你编码人生中所接触到的第一个解耦操作。

2. 分层框架

MVC 是一种十分常见且常用的分层架构,首要包括;M – mode 方针层,封装到 domain 里。V – view 展示层,但由于目前都是前后端别离的项目,几乎不会在后端项目里写 JSP 文件了。C – Controller 操控层,对外供给接口完成类。DAO 算是单独拿出来用户处理数据库操作的层。

  • 如图,在 MVC 的分层架构下。咱们编程3步的所需各类方针、办法、接口,都分配到 MVC 的各个层次中去。
  • 由于这样分层今后,就能够很明晰明晰的知道各个层都在做什么内容,也愈加便利后续的保护和迭代。
  • 关于一个真正的项目来说,是没有一锤子买卖的,最开端的开发远不是本钱地点。最大的开发本钱是后期的保护和迭代。而架构规划的含义更多的便是在处理系统的反复的保护和迭代时,怎么降低本钱,这也是架构分层的含义地点。

3. 调用流程

接下来咱们再看下一套 MVC 架构中各个模块在调用时的串联联系;

  • 以用户建议 HTTP 恳求开端,Controller 在接收到恳求后,调用由 Spring 注入到类里的 Service 办法,进入 Service 办法后有些逻辑会走数据库,有些逻辑是直接内部自己处理后就直接回来给 Controller 了。最终由 Controller 封装成果回来给 HTTP 呼应。
  • 一起咱们也能够看到各个方针在这些恳求间的一个作用,如;恳求方针、库表方针、回来方针。

4. 架构源码

4.1 环境

  • JDK 1.8
  • Maven 3.8.6 – 下载装置maven后,本地记得装备阿里云镜像,便利快速拉取jar包。源码中 docs/maven/settings.xml 有阿里云镜像地址。
  • SpringBoot 2.7.2
  • MySQL 5.7 – 假如你运用 8.0 记得更改 pom.xml 中的 mysql 引证

4.2 架构

  • 源码https://gitcode.net/KnowledgePlanet/road-map/xfg-frame-mvc
  • 树形装置 brew install tree IntelliJ IDEA Terminal 运用 tree
.
├── docs
│   └── mvc.drawio - 架构文档
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── cn
│   │   │       └── bugstack
│   │   │           └── xfg
│   │   │               └── frame
│   │   │                   ├── Application.java
│   │   │                   ├── common
│   │   │                   │   ├── Constants.java
│   │   │                   │   └── Result.java
│   │   │                   ├── controller
│   │   │                   │   └── UserController.java
│   │   │                   ├── dao
│   │   │                   │   └── IUserDao.java
│   │   │                   ├── domain
│   │   │                   │   ├── po
│   │   │                   │   │   └── User.java
│   │   │                   │   ├── req
│   │   │                   │   │   └── UserReq.java
│   │   │                   │   ├── res
│   │   │                   │   │   └── UserRes.java
│   │   │                   │   └── vo
│   │   │                   │       └── UserInfo.java
│   │   │                   └── service
│   │   │                       ├── IUserService.java
│   │   │                       └── impl
│   │   │                           └── UserServiceImpl.java
│   │   └── resources
│   │       ├── application.yml
│   │       └── mybatis
│   │           ├── config
│   │           │   └── mybatis-config.xml
│   │           └── mapper
│   │               └── User_Mapper.xml
│   └── test
│       └── java
│           └── cn
│               └── bugstack
│                   └── xfg
│                       └── frame
│                           └── test
│                               └── ApiTest.java
└── road-map.sql

以上是整个🏭工程架构的 tree 树形图。整个工程由 SpringBoot 驱动。

  • Application.java 是发动程序的 SpringBoot 运用
  • common 是额定增加的一个层,用于界说通用的类
  • controller 操控层,供给接口完成。
  • dao 数据库操作层
  • domain 方针界说层
  • service 服务完成层

5. 测验验证

  • 首要;整个工程由 SpringBoot 驱动,供给了 road-map.sql 测验 SQL 库表句子。你能够在自己的本地mysql上进行履行。它会创立库表。
  • 之后;在 application.yml 装备数据库链接信息。
  • 之后就能够翻开 ApiTest 进行测验了。你能够点击 Application 类的绿色箭头发动工程,运用 UserController 类供给接口的办法调用程序;http://localhost:8089/queryUserInfo

– 假如你正常获取了这样的成果信息,那么阐明你现已发动成功。接下来就能够对照着MVC的结构进行学习,以及运用这样的工程结构开发自己的项目。

二、DDD 架构

从最早接触 DDD 架构,到后来用 DDD 架构不断的接受项目开发,一次次在项目开发中的经历积累。对 DDD 有了不少的了解。DDD 是一种思想,落地的形状和结构会有不同的办法,乃至在编码上也会有风格的差异。但终期方针就一个;”供给代码的可保护性,降低迭代开发本钱。“也是康威规律所述:”任何组织在规划一套系统时,所交付的规划方案在结构上都与该组织的交流结构保持一致。“

但 DDD 与 MVC 相比的概率较多,贸然用理论驱动代码开发,会让整个工程变得十分混乱,乃至或许虽然是用的 DDD 但最终写出来了一片怪样子的 MVC 代码。所以关于程序员👨🏻‍💻来说,先能上手一个工程,在从工程了解理论会愈加简略。为此小傅哥想以此文,经过实战编码的办法向咱们共享 DDD 架构,并能让咱们上手的 DDD 架构。

1. 问题磕碰

你用 MVC 写代码,遇到过最大的问题是什么?🤔

简略、简略、好了解,是 MVC 架构的特色,但也正由于简略的分层逻辑,在适配较杂乱的场景而且需求长周期的保护时,代码的迭代本钱就会越来越高。如图;

  • 假如你接触过较大型且现已长时间保护项目的 MVC 架构,你就会发现这儿的 DAO、PO、VO 方针,在 Service 层相互调用。那么长时间开发后,就导致了各个 PO 里的特色字段数量都被撑的特别大。这样的开发办法,将”状态”“行为“别离到不同的方针中,代码的目的逐渐模糊,膨胀、臃肿和不稳定的架构,让迭代本钱增加。
  • 而 DDD 架构首要以处理此类问题为主,将各个归于自己范畴范围内的行为和逻辑封装到自己的范畴包下处理。这也是 DDD 架构规划的精华之一。它期望在分治层面合理切开问题空间为更小规划的若干子问题,而问题越小就简略被了解和处理,做到高内聚低耦合。这也是康威规律所说到的,处理杂乱场景的规划首要分为:分治、笼统和知识。

2. 简化了解

在给咱们解说 MVC 架构的时分,小傅哥说到了一个简略的开发模型。开发代码能够了解为:“界说特色 -> 创立办法 -> 调用展示” 但这个模型结构过于简略,不太合适运用了各类分布式技术栈以及更多逻辑的 DDD 架构。所以在 DDD 这儿,咱们把开发代码能够笼统为:“触发 -> 函数 -> 衔接” 如图;

  • DDD 架构常用于微服务场景,因而也一个系统的调用办法就不仅仅 HTTP 还包括;RPC 长途MQ 消息TASK 使命,因而这些种办法都能够了解为触发。
  • 经过触发调用函数办法,咱们这儿能够把各个服务都当成一个函数办法来看。而函数办法经过衔接,调用到其他的接口、数据库、缓存来完成函数逻辑。

接下来,小傅哥在带着咱们把这些所需的模块,拆分到对应的DDD系统架构中。

3. 架构分层

如下是 DDD 架构的一种分层结构,也能够有其他种办法,中心的要点在于合适你地点场景的事务开发。以下的分层结构,是小傅哥在运用 DDD 架构多种的办法开发代码后,做了简化和处理的。右侧的连线是各个模块的依靠联系。接下来小傅哥就给咱们做一下模块的介绍。

  • 接口界说 – xfg-frame-api:由于微服务中引证的 RPC 需求对外供给接口的描绘信息,也便是调用方在运用的时分,需求引进 Jar 包,让调用方好能依靠接口的界说做代理。
  • 运用封装 – xfg-frame-app:这是运用发动和装备的一层,如一些 aop 切面或许 config 装备,以及打包镜像都是在这一层处理。你能够把它了解为专门为了发动服务而存在的。
  • 范畴封装 – xfg-frame-domain:范畴模型服务,是一个十分重要的模块。不管怎么做DDD的分层架构,domain 都是必定存在的。在一层中会有一个个细分的范畴服务,在每个服务包中会有【模型、仓库、服务】这样3部分。
  • 仓储服务 – xfg-frame-infrastructure:根底层依靠于 domain 范畴层,由于在 domain 层界说了仓储接口需求在根底层完成。这是依靠倒置的一种规划办法。
  • 范畴封装 – xfg-frame-trigger触发器层,一般也被叫做 adapter 适配器层。用于供给接口完成、消息接收、使命履行等。所以关于这样的操作,小傅哥把它叫做触发器层。
  • 类型界说 – xfg-frame-types:通用类型界说层,在咱们的系统开发中,会有很多类型的界说,包括;根本的 Response、Constants 和枚举。它会被其他的层进行引证运用。
  • 范畴编排【可选】 – xfg-frame-case:范畴编排层,一般关于较大且杂乱的的项目,为了更好的防腐和供给通用的服务,一般会增加 case/application 层,用于对 domain 范畴的逻辑进行封装组合处理。

4. 架构源码

4.1 环境

  • JDK 1.8
  • Maven 3.8.6
  • SpringBoot 2.7.2
  • MySQL 5.7 – 假如你运用 8.0 记得更改 pom.xml 中的 mysql 引证

4.2 架构

  • 源码https://gitcode.net/KnowledgePlanet/road-map/xfg-frame-ddd
  • 树形装置 brew install tree IntelliJ IDEA Terminal 运用 tree
.
├── README.md
├── docs
│   ├── dev-ops
│   │   ├── environment
│   │   │   └── environment-docker-compose.yml
│   │   ├── siege.sh
│   │   └── skywalking
│   │       └── skywalking-docker-compose.yml
│   ├── doc.md
│   ├── sql
│   │   └── road-map.sql
│   └── xfg-frame-ddd.drawio
├── pom.xml
├── xfg-frame-api
│   ├── pom.xml
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── cn
│   │               └── bugstack
│   │                   └── xfg
│   │                       └── frame
│   │                           └── api
│   │                               ├── IAccountService.java
│   │                               ├── IRuleService.java
│   │                               ├── model
│   │                               │   ├── request
│   │                               │   │   └── DecisionMatterRequest.java
│   │                               │   └── response
│   │                               │       └── DecisionMatterResponse.java
│   │                               └── package-info.java
│   └── xfg-frame-api.iml
├── xfg-frame-app
│   ├── Dockerfile
│   ├── build.sh
│   ├── pom.xml
│   ├── src
│   │   ├── main
│   │   │   ├── bin
│   │   │   │   ├── start.sh
│   │   │   │   └── stop.sh
│   │   │   ├── java
│   │   │   │   └── cn
│   │   │   │       └── bugstack
│   │   │   │           └── xfg
│   │   │   │               └── frame
│   │   │   │                   ├── Application.java
│   │   │   │                   ├── aop
│   │   │   │                   │   ├── RateLimiterAop.java
│   │   │   │                   │   └── package-info.java
│   │   │   │                   └── config
│   │   │   │                       ├── RateLimiterAopConfig.java
│   │   │   │                       ├── RateLimiterAopConfigProperties.java
│   │   │   │                       ├── ThreadPoolConfig.java
│   │   │   │                       ├── ThreadPoolConfigProperties.java
│   │   │   │                       └── package-info.java
│   │   │   └── resources
│   │   │       ├── application-dev.yml
│   │   │       ├── application-prod.yml
│   │   │       ├── application-test.yml
│   │   │       ├── application.yml
│   │   │       ├── logback-spring.xml
│   │   │       └── mybatis
│   │   │           ├── config
│   │   │           │   └── mybatis-config.xml
│   │   │           └── mapper
│   │   │               ├── RuleTreeNodeLine_Mapper.xml
│   │   │               ├── RuleTreeNode_Mapper.xml
│   │   │               └── RuleTree_Mapper.xml
│   │   └── test
│   │       └── java
│   │           └── cn
│   │               └── bugstack
│   │                   └── xfg
│   │                       └── frame
│   │                           └── test
│   │                               └── ApiTest.java
│   └── xfg-frame-app.iml
├── xfg-frame-ddd.iml
├── xfg-frame-domain
│   ├── pom.xml
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── cn
│   │               └── bugstack
│   │                   └── xfg
│   │                       └── frame
│   │                           └── domain
│   │                               ├── order
│   │                               │   ├── model
│   │                               │   │   ├── aggregates
│   │                               │   │   │   └── OrderAggregate.java
│   │                               │   │   ├── entity
│   │                               │   │   │   ├── OrderItemEntity.java
│   │                               │   │   │   └── ProductEntity.java
│   │                               │   │   ├── package-info.java
│   │                               │   │   └── valobj
│   │                               │   │       ├── OrderIdVO.java
│   │                               │   │       ├── ProductDescriptionVO.java
│   │                               │   │       └── ProductNameVO.java
│   │                               │   ├── repository
│   │                               │   │   ├── IOrderRepository.java
│   │                               │   │   └── package-info.java
│   │                               │   └── service
│   │                               │       ├── OrderService.java
│   │                               │       └── package-info.java
│   │                               ├── rule
│   │                               │   ├── model
│   │                               │   │   ├── aggregates
│   │                               │   │   │   └── TreeRuleAggregate.java
│   │                               │   │   ├── entity
│   │                               │   │   │   ├── DecisionMatterEntity.java
│   │                               │   │   │   └── EngineResultEntity.java
│   │                               │   │   ├── package-info.java
│   │                               │   │   └── valobj
│   │                               │   │       ├── TreeNodeLineVO.java
│   │                               │   │       ├── TreeNodeVO.java
│   │                               │   │       └── TreeRootVO.java
│   │                               │   ├── repository
│   │                               │   │   ├── IRuleRepository.java
│   │                               │   │   └── package-info.java
│   │                               │   └── service
│   │                               │       ├── engine
│   │                               │       │   ├── EngineBase.java
│   │                               │       │   ├── EngineConfig.java
│   │                               │       │   ├── EngineFilter.java
│   │                               │       │   └── impl
│   │                               │       │       └── RuleEngineHandle.java
│   │                               │       ├── logic
│   │                               │       │   ├── BaseLogic.java
│   │                               │       │   ├── LogicFilter.java
│   │                               │       │   └── impl
│   │                               │       │       ├── UserAgeFilter.java
│   │                               │       │       └── UserGenderFilter.java
│   │                               │       └── package-info.java
│   │                               └── user
│   │                                   ├── model
│   │                                   │   └── valobj
│   │                                   │       └── UserVO.java
│   │                                   ├── repository
│   │                                   │   └── IUserRepository.java
│   │                                   └── service
│   │                                       ├── UserService.java
│   │                                       └── impl
│   │                                           └── UserServiceImpl.java
│   └── xfg-frame-domain.iml
├── xfg-frame-infrastructure
│   ├── pom.xml
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── cn
│   │               └── bugstack
│   │                   └── xfg
│   │                       └── frame
│   │                           └── infrastructure
│   │                               ├── dao
│   │                               │   ├── IUserDao.java
│   │                               │   ├── RuleTreeDao.java
│   │                               │   ├── RuleTreeNodeDao.java
│   │                               │   └── RuleTreeNodeLineDao.java
│   │                               ├── package-info.java
│   │                               ├── po
│   │                               │   ├── RuleTreeNodeLineVO.java
│   │                               │   ├── RuleTreeNodeVO.java
│   │                               │   ├── RuleTreeVO.java
│   │                               │   └── UserPO.java
│   │                               └── repository
│   │                                   ├── RuleRepository.java
│   │                                   └── UserRepository.java
│   └── xfg-frame-infrastructure.iml
├── xfg-frame-trigger
│   ├── pom.xml
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── cn
│   │               └── bugstack
│   │                   └── xfg
│   │                       └── frame
│   │                           └── trigger
│   │                               ├── http
│   │                               │   ├── Controller.java
│   │                               │   └── package-info.java
│   │                               ├── mq
│   │                               │   └── package-info.java
│   │                               ├── rpc
│   │                               │   ├── AccountService.java
│   │                               │   ├── RuleService.java
│   │                               │   └── package-info.java
│   │                               └── task
│   │                                   └── package-info.java
│   └── xfg-frame-trigger.iml
└── xfg-frame-types
    ├── pom.xml
    ├── src
    │   └── main
    │       └── java
    │           └── cn
    │               └── bugstack
    │                   └── xfg
    │                       └── frame
    │                           └── types
    │                               ├── Constants.java
    │                               ├── Response.java
    │                               └── package-info.java
    └── xfg-frame-types.iml

以上是整个🏭工程架构的 tree 树形图。整个工程由 xfg-frame-app 模的 SpringBoot 驱动。这儿小傅哥在 domain 范畴模型下供给了 order、rule、user 三个范畴模块。并在每个模块下供给了对应的测验内容。这块是整个模型的要点,其他模块都能够经过测验看到这儿的调用进程。

4.3 范畴

一个范畴模型中包括3个部分;model、repository、service 三部分;

  • model 方针的界说
  • repository 仓储的界说
  • service 服务完成

以上3个模块,一般也是咱们在运用 DDD 时分最不简略了解的分层。比方 model 里还分为;valobj – 值方针、entity 实体方针、aggregates 聚合方针;

  • 值方针:表示没有仅有标识的事务实体,例如产品的名称、描绘、价格等。
  • 实体方针:表示具有仅有标识的事务实体,例如订单、产品、用户等;
  • 聚合方针:是一组相关的实体方针的根,用于保证实体方针之间的一致性和完整性;

关于model中各个方针的拆分,尤其是聚合的界说,会牵引着整个模型的规划。当然你能够在初期运用 DDD 的时分不必过火在意范畴模型的规划,能够把整个 domain 下的一个个包当做充血模型结构,这样编写出来的代码也是十分合适保护的。

4.4 环境(开发/测验/上线)

源码xfg-frame-ddd/pom.xml

<profile>
    <id>dev</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
        <profileActive>dev</profileActive>
    </properties>
</profile>
<profile>
    <id>test</id>
    <properties>
        <profileActive>test</profileActive>
    </properties>
</profile>
<profile>
    <id>prod</id>
    <properties>
        <profileActive>prod</profileActive>
    </properties>
</profile>
  • 界说环境;开发、测验、上线。

源码xfg-frame-app/application.yml

spring:
  config:
    name: xfg-frame
  profiles:
    active: dev # dev、test、prod
  • 除了 pom 的装备,还需求在 application.yml 中指定环境。这样就能够对应的加载到;application-dev.ymlapplication-prod.ymlapplication-test.yml 这样就能够很便利的加载对应的装备信息了。尤其是各个场景中切换会愈加便利。

4.5 切面

一个工程开发中,有时分或许会有很多的统一切面和发动装备的处理,这些内容都能够在 xfg-frame-app 完成。

源码cn.bugstack.xfg.frame.aop.RateLimiterAop

@Slf4j
@Aspect
public class RateLimiterAop {
    private final long timeout;
    private final double permitsPerSecond;
    private final RateLimiter limiter;
    public RateLimiterAop(double permitsPerSecond, long timeout) {
        this.permitsPerSecond = permitsPerSecond;
        this.timeout = timeout;
        this.limiter = RateLimiter.create(permitsPerSecond);
    }
    @Pointcut("execution(* cn.bugstack.xfg.frame.trigger..*.*(..))")
    public void pointCut() {
    }
    @Around(value = "pointCut()", argNames = "jp")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        boolean tryAcquire = limiter.tryAcquire(timeout, TimeUnit.MILLISECONDS);
        if (!tryAcquire) {
            Method method = getMethod(jp);
            log.warn("办法 {}.{} 恳求已被限流,超越限流装备[{}/秒]", method.getDeclaringClass().getCanonicalName(), method.getName(), permitsPerSecond);
            return Response.<Object>builder()
                    .code(Constants.ResponseCode.RATE_LIMITER.getCode())
                    .info(Constants.ResponseCode.RATE_LIMITER.getInfo())
                    .build();
        }
        return jp.proceed();
    }
    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }
}

运用

# 限流装备
rate-limiter:
  permits-per-second: 1
  timeout: 5
  • 这样你所有的通用装备,又和事务没有太大的联系的,就能够直接写到这儿了。—— 详细能够参阅代码。

5. 测验验证

  • 首要;整个工程由 SpringBoot 驱动,供给了 road-map.sql 测验 SQL 库表句子。你能够在自己的本地mysql上进行履行。它会创立库表。
  • 之后;在 application.yml 装备数据库链接信息。
  • 之后就能够翻开 ApiTest 进行测验了。你能够点击 Application 类的绿色箭头发动工程,运用触发器里的接口调用测验,或许单元测验RPC接口,小傅哥也供给了泛化调用的办法。
  • 假如你正常获取了这样的成果信息,那么阐明你现已发动成功。接下来就能够对照着DDD的结构进行学习,以及运用这样的工程结构开发自己的项目。

三、实战 – DDD 项目

纸上得来终觉浅,码农学习要实战!

不管是 MVC 仍是各类 DDD 所出现的架构,仍是需求看到实践的代码,以及参加实战开发才能更好的吸收。不然都是理论仍旧难以让人下手。

所以小傅哥为咱们预备了一些学习项目,这些项目都是十分具有架构思想以及规划形式的运用级实战项目架构规划和落地。关于一些小白来说,假如能早早的接触到这样的项目,就相当于是提前进入企业实习了。能够极大的说到编程思想以及开发能力。

这些项目包括:《Lottery 抽奖系统 – 基于范畴驱动规划的四层架构实践》、《API网关:中间件规划和落地》、《ChatGPT 微服务运用系统搭建》、《IM 仿微信》、《SpringBoot Starter 中间件规划和落地》等。这儿小傅哥只列3张图,你就知道有多牛皮了!

第1张:Lottery

架构

工程

第2张:API网关

架构

工程

第3张:ChatGPT


实战项目:bugstack.cn/md/zsxq/int…

此外,小傅哥还给咱们预备了一系列的《Java简明教程》视频,进入B站即可学习!

www.bilibili.com/video/BV1kV…