今天要分享的是 MVC 和 DDD 的架构本质,通过由浅入深的介绍讲解和视频带着手把手操作创建工程架构。让无论是学习 MVC 的小白码农还是希望了解更多关于 DDD 内容的老白码农,都可以学习到一点自己需要的内容。
如果我们尝试把编程的复杂架构缩小到最容易理解的程度,那么编程开发其实只做3件事:”定义属性
、创建方法
、调用展示
“。但因为同类所需的内容较多,如一系列的属性,一堆的方法实现,一组的接口封装,那么就需要合理的把这些内容分配到不同的层次中去实现,因此有了分层架构的设计。
那么本文小傅哥会向大家介绍一套MVC架构的分层设计以及如何创建使用,并提供相应的简单的案例。你可以复制这套架构在自己的场景中使用,也更能方便编程的小白可以更快的上手开发。
注意:此套MVC架构模型适合提供HTTP服务的工程架构,适合简单的小场景开发使用。特点;轻便、简单、学习成本低。
如果说你是一个特别小的玩具项目
,你甚至可以把编程的3步写到一个类里。但因为你做的是正经项目,你的各种类;对象类、库表类、方法类,就会成群结队的来。如果你想把这些成群结队的类的内容,都写到一个类里去,那么就是几万行的代码了。—— 当然你也可以吹牛逼,你一个人做过一个项目,这项目大到啥程度呢。就是有一个类里有上万行代码。
所以,为了不至于让一个类撑到爆,需要把黄色的对象、绿色的方法、红色的接口,都分配到不同的包结构下。这就是你编码人生中所接触到的第一个解耦操作。
MVC 是一种非常常见且常用的分层架构,主要包括;M - mode 对象层,封装到 domain 里。V - view 展示层,但因为目前都是前后端分离的项目,几乎不会在后端项目里写 JSP 文件了。C - Controller 控制层,对外提供接口实现类。DAO 算是单独拿出来用户处理数据库操作的层。
接下来我们再看下一套 MVC 架构中各个模块在调用时的串联关系;
docs/maven/settings.xml
有阿里云镜像地址。安装 brew install tree
IntelliJ IDEA Terminal 使用 tree
java
复制代码
. ├── 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 驱动。
- 如果你正常获取了这样的结果信息,那么说明你已经启动成功。接下来就可以对照着MVC的结构进行学习,以及使用这样的工程结构开发自己的项目。
从最早接触 DDD 架构,到后来用 DDD 架构不断的承接项目开发,一次次在项目开发中的经验积累。对 DDD 有了不少的理解。DDD 是一种思想,落地的形态和结构会有不同的方式,甚至在编码上也会有风格的差异。但终期目标就一个;”提供代码的可维护性,降低迭代开发成本。“也是康威定律所述:”任何组织在设计一套系统时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。“
但 DDD 与 MVC 相比的概率较多,贸然用理论驱动代码开发,会让整个工程变得非常混乱,甚至可能虽然是用的 DDD 但最后写出来了一片四不像的 MVC 代码。所以对于程序员来说,先能上手一个工程,在从工程了解理论会更加容易。为此小傅哥想以此文,通过实战编码的方式向大家分享 DDD 架构,并能让大家上手的 DDD 架构。
你用 MVC 写代码,遇到过最大的问题是什么?
简单、容易、好理解,是 MVC 架构的特点,但也正因为简单的分层逻辑,在适配较复杂的场景并且需要长周期的维护时,代码的迭代成本就会越来越高。如图;
”状态”
、“行为“
分离到不同的对象中,代码的意图渐渐模糊,膨胀、臃肿和不稳定的架构,让迭代成本增加。在给大家讲解 MVC 架构的时候,小傅哥提到了一个简单的开发模型。开发代码可以理解为:“定义属性 -> 创建方法 -> 调用展示”
但这个模型结构过于简单,不太适合运用了各类分布式技术栈以及更多逻辑的 DDD 架构。所以在 DDD 这里,我们把开发代码可以抽象为:“触发 -> 函数 -> 连接”
如图;
RPC 远程
、MQ 消息
、TASK 任务
,因此这些种方式都可以理解为触发。接下来,小傅哥在带着大家把这些所需的模块,拆分到对应的DDD系统架构中。
如下是 DDD 架构的一种分层结构,也可以有其他种方式,核心的重点在于适合你所在场景的业务开发。以下的分层结构,是小傅哥在使用 DDD 架构多种的方式开发代码后,做了简化和处理的。右侧的连线是各个模块的依赖关系。接下来小傅哥就给大家做一下模块的介绍。
安装 brew install tree
IntelliJ IDEA Terminal 使用 tree
java
复制代码
. ├── 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 三个领域模块。并在每个模块下提供了对应的测试内容。这块是整个模型的重点,其他模块都可以通过测试看到这里的调用过程。
一个领域模型中包含3个部分;model、repository、service 三部分;
以上3个模块,一般也是大家在使用 DDD 时候最不容易理解的分层。比如 model 里还分为;valobj - 值对象、entity 实体对象、aggregates 聚合对象;
关于model中各个对象的拆分,尤其是聚合的定义,会牵引着整个模型的设计。当然你可以在初期使用 DDD 的时候不用过分在意领域模型的设计,可以把整个 domain 下的一个个包当做充血模型结构,这样编写出来的代码也是非常适合维护的。
源码:xfg-frame-ddd/pom.xml
pom
复制代码
源码:xfg-frame-app/application.yml
java
复制代码
spring: config: name: xfg-frame profiles: active: dev # dev、test、prod
application-dev.yml
、application-prod.yml
、application-test.yml
这样就可以很方便的加载对应的配置信息了。尤其是各个场景中切换会更加方便。一个工程开发中,有时候可能会有很多的统一切面和启动配置的处理,这些内容都可以在 xfg-frame-app 完成。
源码:cn.bugstack.xfg.frame.aop.RateLimiterAop
java
复制代码
@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.
使用
java
复制代码
# 限流配置 rate-limiter: permits-per-second: 1 timeout: 5
纸上得来终觉浅,码农学习要实战!
无论是 MVC 还是各类 DDD 所呈现的架构,还是需要看到实际的代码,以及参与实战开发才能更好的吸收。否则都是理论仍旧难以让人下手。
所以小傅哥为大家准备了一些学习项目,这些项目都是非常具有架构思维以及设计模式的应用级实战项目架构设计和落地。对于一些小白来说,如果能早早的接触到这样的项目,就相当于是提前进入企业实习了。可以极大的提到编程思维以及开发能力。
这些项目包括:《Lottery 抽奖系统 - 基于领域驱动设计的四层架构实践》、《API网关:中间件设计和落地》、《ChatGPT 微服务应用体系搭建》、《IM 仿微信》、《SpringBoot Starter 中间件设计和落地》等。这里小傅哥只列3张图,你就知道有多牛皮了!
架构
工程
架构
工程