项目架构MVC,DDD学习
写在前面
本文一起看下项目架构DDD,MVC相关的内容。
1:MVC
不管我们做什么项目,自己想想其实只是做了三件事,如下:
其实,这三件事完全在一个类中做完也可以可以正常把项目完成的,就像下面这样:
@RequestMapping("/xxx")
public void XxxHttp {private String userId;private String username;public Response queryUerById(String userId) {queryFromDb(userId);Response res = assembleRes();return res;}// 查询数据public void queryFromDb(String userId) {// 查询数据库的操作,获取username等信息}// 组装响应public Response assembleRes() {}
}
试想下,如果所有的东西全部都塞到一个类里去,那么这个后期项目的可维护性和可扩展性几乎为0。针对这个问题,系统架构就应运而生了,而MVC就是系统架构的一种,当然DDD也是,本文后边部分也会学习到。这里,先来只看MVC相关的内容,MVC通过分层的思想来解耦程序,增加可维护性和可扩展性,把不同的属性分配到不同类型的对象中,把方法进行更细粒度的划分,大概如下图:
对每部分功能进行细分后,MVC同样会将其划分到不同的层,即文件夹中,从而使得结构更加的清晰,基本上如下图:
为了对MVC有一个更加清晰的认识,以springboot的方式来看一个实际的项目例子:
为了方便学习,我录制了一个视频放在这里:
项目架构MVC
视频中MVC系统架构用到的关键层所对应的文件夹都已经创建完毕了。详细的参考下源码 。
再来看下最后的效果:
- 通过单元测试测试
- 访问接口
这样,一个典型的MVC项目我们就完成了!
接着继续来看DDD项目又是咋回事。
2:DDD
源码 。
DDD全称,domain-driven design,即领域驱动设计,是一种系统架构设计的思想,根据这种思想我们可以通过不同的模块来组织我们的项目结构,其中一种可能的组织方式如下:
注意:默认使用spring boot。
app:定义项目全局内容,如启动类,AOP配置,dockerfile,启动脚本等。
types:定义公用的常量类,枚举类,响应结果类,带分页的类等。
api:定义需要对外暴漏的类和接口,如dubbbo的服务描述接口,以及请求和响应中需要用到的类。
domain:最关键的模块,定义出不同业务模块的仓储接口,服务类(给出具体的实现),model(聚合类,entity,valobjs)。
infrastructure:对domain的仓储接口给出具体的实现,以及定义映射数据库表的PO类。
trigger:触发模块,作为触发程序执行的模块,如接收http接口的controller,接收rpc请求的provider,消费消息的消费者,以及自动定时执行的job等。
本文也会按照这种组织方式来共同进行学习。
2.1:app
定义app之前先来看下app模块中写啥东西:
全局的配置可以写在这里,比如:
1:项目级的AOP配置
2:项目级的config配置(比如配置redis的序列化方式等)
3:打包镜像,启动脚本(start.sh等)
2.2:types
定义types之前来看下types模块中写啥东西:
来定义一些通用的类型,比如常量类Constants,接口响应用到的公共对象,
比如定义了响应状态码和data数据ReponseEntity(当然不一定非得是这个名字)对象。
当然其他的只要是各个业务模块都可用用到的公共的类都可以定义在这里,但注意一定要是公用的哟!!!
- 一个可能的常量类
// 你们都甭继承我!!!
public final class Constants {public static final String ERROR_MSG = "你干哈,都给我搞出错了!!!";
}
- 一个可能的封装相应信息的公共类
@Builder
public class ResEntity<T> {private T data;private int code;
}
其他的可根据具体情况定义在这里。
2.3:api
定义api之前来看下api模块中写啥东西:
如外部需要用到的rpc描述文件(需要打成jar包,提供出来),以及描述文件中需要用到的相关类,
如入参以及返回的对象等。需要对外暴漏的类的和接口(如dubbo服务描述接口)
因此,一般该模块是提供出去给外部使用的,当然自己也会使用到。
- 定义可能的dubbo接口
/*** dubbo的某个对外服务接口*/
public interface DubboXxxInterface {DubboReqObj querySth(DubboReqObj dubboReqObj);
}
- 入参和出参可能的用到的类
/*** dubbo的某入参对象(使用者需要知道)*/
public class DubboReqObj {
}/*** dubbo的某出参对象(使用者需要知道)*/
public class DubboResObj {
}
2.4:domain
定义domain之前来看下domain模块中写啥东西:
首先按照业务模块,一个业务模块一个文件夹。在每个文件夹下分别定义的信息如下:
model:同MVC系统架构的domain层定义的内容aggregates:组合valobjs,因为某个业务可能需要多种VO组合在一起才能满足需求,当然也可以聚合entity,所以该类对象就是用来聚合entity和valobjs,从而满足业务对数据需求的的一类对象entity:一般和数据库实体对象是1v1的关系,但是相比与数据库实体PO,只包含其中业务相关的信息,比如id啊,创建时间啊,删除状态啊这些是不需要的,po的一些有限状态的字段在entity中使用枚举来表示,po的逗号分隔的信息在entity使用list形式来表示,总是呢,其实就是将po转换为更加符合业务需要的方式,这种更加符合业务需要的方式就是entity了,比如有PO,信息是private int id, private int status(1代表xxx 2代表xxx), private varchar hobbies(逗号分割),转换为entity就是 private StatusEnum status(1代表xxx 2代表xxx), private List hobbiesList,大概这种!哦,对,entity还有另一个作用对po做防腐,意思就是不要你随便糟蹋,嚯嚯PO同学。valobjs:应该就是VO,给UI使用的
repository: 定义数据库操作的接口,infrastructure会通过依赖倒置的方式来提供具体的实现
service:具体服务代码的实现,同MVC系统架构的service,不过只定义本业务模块的相关服务类,相比于MVC的service范围更小,即是其子集,需要注意名字不一定非得是xxxService,也可以是xxxEgine,xxxFilter,xxxProcessor,xxxHandler等这种,当然为了能够更加清晰也可以再创建对应的engine,filter等子文件夹
- 可能的model信息(聚合对象,entity,valobjs)
- 可能的仓储接口
/*** 用户仓库类*/
public interface IUserRepository {UserAggregates queryUserByIds(List<String> userIdList);
}
- 具体服务类
public interface IUserService {
}public class UserServiceImpl implements IUserService {// 通过spring容器注入在infrastructure模块中提供的仓库具体实现@Resourceprivate IUserRepository userRepository;
}
以上private IUserRepository userRepository
我们会在infrastructure基础设施模块提供具体实现,并交给spring管理。
2.6:infrastructure
定义infrastructure之前来看下infrastructure模块中写啥东西:
1:基于domain层提供数据库操作的具体实现(在domain层中我们定义了数据库操作的接口了不是),与domain是一种依赖倒置的关系
2:和数据库表对应的PO也在这里写
3:对domain定义的仓库接口提供具体实现,因此需要依赖于domain模块,但注意domain模块不能依赖于infrastructure模块,但domain又需要具体的数据库接口实现的dao,咋办呢?通过spring 容器注入即可。
- user表PO
/*** 数据库表user的映射(注意,需要做防腐处理,避免最重要的数据查询出现问题!!!)*/
public class UserPO {/** 用户ID */private Long id;/** 用户名称 */private String userId;/** 用户昵称 */private String userNickname;/** 用户头像 */private String userHead;/** 账号密码 */private String userPassword;/** 创建时间 */private Date createTime;/** 修改时间 */private Date updateTime;}
- dao
@Mapper
public interface IUserDao {List<UserPO> queryUserList();}
- 仓储实现
@Component
public class UserRepository implements IUserRepository {@Resourceprivate IUserDao userDao;@Overridepublic UserAggregates queryUserByIds(List<String> userIdList) {List<UserPO> userPOS = userDao.queryUserList();UserAggregates userAggregates = new UserAggregates();// TODO userPOS -> userAggregates 略!for (UserPO userPO : userPOS) {System.out.println(userPO);}return userAggregates;}
}
2.7:trigger
定义trigger之前来看下trigger模块中写啥东西:
写触发程序执行的相关内容,如
1:接收http请求的controller
2:接收mq消息的消费者
3:rpc调用的server(如dubbo server)
4:定时任务(如自己定义的timer,或者是等待xxx-job等分布式任务调度框架执行的代码)
5;其他等
- http
@RestController
public class Controller {// 注入domain的service实现类就可以编写具体的处理逻辑了,这和常规的mvc就一样了!!!@Resourceprivate IUserService userService;@RequestMapping("/user")public ResEntity<String> queryUser() {// TODO 使用 userService写具体的逻辑,略!!!return ResEntity.<String>builder().data("cccccc").build();}
}
- rpc
/*** 某dubbo的provider*/
public class XxxDubboProvider {
}
- timer
/*** 某定时执行的任务*/
public class SomeXxxJob {
}
为了更好的帮助你理解DDD的内容,我录制了一个视频,放在这里:
项目架构DDD,手把手从零搭建一个落地的DDD项目,没有枯燥的理论,就是写代码!!!
写在后面
参考文章列表
架构的本质之MVC架构 —— Java简明教程,一套简单、清晰、明了的Java学习路线资料!!! 。
相关文章:

项目架构MVC,DDD学习
写在前面 本文一起看下项目架构DDD,MVC相关的内容。 1:MVC 不管我们做什么项目,自己想想其实只是做了三件事,如下: 其实,这三件事完全在一个类中做完也可以可以正常把项目完成的,就像下面这…...
SQLite的PRAGMA 声明
PRAGMA 语句是特定于 SQLite 的 SQL 扩展,用于 修改 SQLite 库的操作或查询 SQLite 库 内部(非表)数据。PRAGMA声明使用相同的 接口作为其他 SQLite 命令(例如 SELECT、INSERT)但 在以下重要方面有所不同: …...

使用ArrayList.removeAll(List list)导致的机器重启
背景 先说一下背景,博主所在的业务组有一个核心系统,需要同步两个不同数据源给过来的数据到redis中,但是每次同步之前需要过滤掉一部分数据,只存储剩下的数据。每次同步的数据与需要过滤掉的数据量级大概在0-100w的数据不等。 由…...
如何在项目中使用uni-ui组件库
1、安装uni-ui npm i dcloudio/uni-ui 2、组件自动引用 配置easycom 使用 npm 安装好 uni-ui 之后,需要配置 easycom 规则,让 npm 安装的组件支持 easycom 打开项目根目录下的 pages.json 并添加 easycom 节点: // pages.json {"e…...
redis的过期策略和内存淘汰机制(redis篇)
分享并学习一下redis的过期策略和内存淘汰机制 在平时的工作或者学习中,即便自己没有实打实的用过redis。但是能有对这方面的思考,再结合一些实际场景和理论,那么我相信自己或者你都会越来越厉害的。 首先,我们需要认清为啥redis要…...
Java中Runnable和Callable有什么不同?(企业真题)
Java中Runnable和Callable有什么不同? 与之前的方式的对比:与Runnable方式的对比的好处 call()可以有返回值,更灵活 call()可以使用throws的方式处理异常,更灵活 Callable使用了泛型参数,可以指明具体的call()的返回值…...

图机器学习导论
图:描述关系数据的通用语言,起源于哥尼斯堡七桥问题 传统的机器学习:数据样本之间独立同分布,简单拟合数据边界,在传统的机器学习中,每个数据样本彼此无关。传统的神经网络,只能处理简单的表格、…...

地推网推拉新平台哪家强?一文清楚告诉你
在当今这个充满副业的时代,地推网推拉新平台的寻找与对接成为了许多人关注的焦点。那么,我们应该如何找到那些既靠谱又有潜力的拉新项目呢? 经过深入研究和全网检索,我为大家盘点了5个值得一试地推网推拉新平台。 尤其是“聚小推…...

Day:004(4) | Python爬虫:高效数据抓取的编程技术(数据解析)
XPath工具 浏览器-元素-CtrlF 浏览器-控制台- $x(表达式) Xpath helper (安装包需要科学上网) 问题 使用离线安装包 出现 程序包无效 解决方案 使用修改安装包的后缀名为 rar,解压文件到一个文件夹,再用 加载文件夹的方式安装即可 安装 python若使用…...
(80) 只出现一次的数字(81)反转字符串
文章目录 1. 每日一言2. (80) 只出现一次的数字2.1 解题思路2.2 代码 3. (81)反转字符串3.1 解题思路3.2 代码 4. 结语 1. 每日一言 生活是一场即兴表演,值得庆幸的是我们总是有所感受,并且将一直感受下去。 2. (80) 只出现一次的数字 题目链接&#x…...

基于拉格朗日分布算法的电动汽车充放电调度MATLAB程序
微❤关注“电气仔推送”获得资料(专享优惠) 程序简介 该模型主要做的是基于拉格朗日分布算法的电动汽车充放电调度模型。利用蒙特卡洛模拟法模拟出电动汽车负荷曲线,并求解出无序充电功率曲线和有序充电曲线,该模型在电动汽车个…...

【Linux 学习】进程优先级和命令行参数!
1. 什么是优先级? 指定进程获取某种资源(CPU)的先后顺序; Linux 中优先级数字越小,优先级越高; 1.1 优先级和权限的区别? 权限 : 能不能做 优先级: 已经能了,但是获…...
Git删除未跟踪的文件Untracked files
在 Git 中,要删除未跟踪的文件(Untracked files),你可以使用 git clean 命令。请注意,这个命令会从你的工作目录中永久删除这些文件,因此在执行之前请确保你不再需要这些文件或已经妥善备份。 以下是如何使…...
S7-1200PLC控制V90伺服通过FB284实现位置控制的方法
S7-1200PLC控制V90伺服通过FB284实现位置控制的方法 通过西门子报文111和FB284功能块 在V-ASSISTANT中将V90 PN设置控制模式为"基本位置控制(EPOS)" V90 PN与PLC采用PROFINET RT通信方式并使用西门子报文111。 在博途中V90 PN的设备视图中更改报文为:报文111 安装…...

2024年阿里云优惠券领取和使用方法
阿里云优惠代金券领取入口,阿里云服务器优惠代金券、域名代金券,在领券中心可以领取当前最新可用的满减代金券,阿里云百科aliyunbaike.com分享阿里云服务器代金券、领券中心、域名代金券领取、代金券查询及使用方法: 阿里云优惠券…...

工业项目中你连PLM系统都没见过?
什么是 PLM 软件? PLM 软件是用于管理全球供应链中产品或服务全生命周期环节的解决方案。它包括从物料、零部件、产品、文档、规定、工程变更单到质量工作流的数据管理。 PLM 的发展历史 从最初的产品设计管理到如今的数字化转型和智能化生产,PLM 在不断…...

【QT入门】 Qt自定义控件与样式设计之QPushButton实现鼠标悬浮按钮弹出对话框
往期回顾: 【QT入门】 Qt自定义控件与样式设计之qss选择器-CSDN博客 【QT入门】 Qt自定义控件与样式设计之QLineEdit的qss使用-CSDN博客 【QT入门】Qt自定义控件与样式设计之QPushButton常用qss-CSDN博客 【QT入门】 Qt自定义控件与样式设计之QPushButton实现鼠标悬…...

C盘变红怎么办?免费的系统C盘清理方法,C盘空间占用克星
百夫说:分享免费又好用的工具,是一件快乐的事情。 正文: 起因:C盘报警,系统变慢 立即下载XX系统清理大师,搜索出垃圾数据近30G,开心的点击“一键清理”,结果提示要收费:…...
简述VPS 与 Apache 搭建网站方式对比:新手科普指南
在互联网时代,拥有一个网站对于个人、企业以及组织来说已经成为了必备的一项资源。然而,对于新手来说,如何搭建一个网站可能是一个挑战。在这篇文章中,我将探讨两种常见的搭建网站的方式:使用虚拟专用服务器࿰…...
js获取年月份
一、date 如何使用、如何获取年月日时分秒、时间戳、如何获取指定日期的时间戳或周几 1..Date 对象用于处理日期和时间。 创建 Date 对象的语法: var myDatenew Date() 获取年月日时分秒: // 格式化日对象 const getNowDate () > {let date new …...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

2.3 物理层设备
在这个视频中,我们要学习工作在物理层的两种网络设备,分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间,需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质,假设A节点要给…...
node.js的初步学习
那什么是node.js呢? 和JavaScript又是什么关系呢? node.js 提供了 JavaScript的运行环境。当JavaScript作为后端开发语言来说, 需要在node.js的环境上进行当JavaScript作为前端开发语言来说,需要在浏览器的环境上进行 Node.js 可…...
用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法
用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法 大家好,我是Echo_Wish。最近刷短视频、看直播,有没有发现,越来越多的应用都开始“懂你”了——它们能感知你的情绪,推荐更合适的内容,甚至帮客服识别用户情绪,提升服务体验。这背后,神经网络在悄悄发力,撑起…...
Python爬虫实战:研究Restkit库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...