# archetype-mvc
**Repository Path**: chenQiqao/archetype-mvc
## Basic Information
- **Project Name**: archetype-mvc
- **Description**: 完全基于MVC模式的单体到微服务动态扩容架构
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2026-01-20
- **Last Updated**: 2026-01-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 基于MVC模式下的单体和微服务之间动态切换的项目代码架构
### 简介
简易型基于`mvc`模式下,能单体和微服务之间动态切换的项目代码架构。进阶版的[https://gitee.com/JayEinstein/archetype](https://gitee.com/JayEinstein/archetype)
### 前言
个人进行微服务开发中,由于总是碰到服务与服务之间相互耦合的业务场景,从而导致一个简单的需求实现的复杂度呈现出倍数的难度,进而总结出来的一个可单体与微服务动态切换的项目代码架构。
个人认为代码的本身应该是一个整体,不应该由于服务或者应用的独立从而形成代码之间的割裂,代码作为程序员输出的重要资产,要使用模块化对其进行管理和开发。
这里进行了基于MVC模式下的单体和微服务之间动态切换的项目代码架构管理演示:
### 一、模块的划分
这里演示的是商城项目,由产品模块(product)、会员模块(member)、订单模块(order)组成,实现一个简单的下单功能,需要校验会员信息,获取产品信息,扣减库存,最后生成订单。
项目结构:
```
archetype-mvc
+- shop-common
+- shop-config
+- shop-controller
| +- member-controller
| +- order-controller
| +- product-controller
+- shop-dao
+- shop-dependencies
+- shop-model
+- shop-rpc-frame
+- shop-service
| +- member-service
| +- order-service
| +- product-service
+- shop-service-dao
| +- member-service-dao
| +- order-service-dao
| +- product-service-dao
+- start
| +- start-all
| +- start-member
| +- start-order
| +- start-product
+- start-gateway
| +- app-gateway
| +- bops-gateway
```
我们熟悉的mvc模式是由`controller`-`service`-`dao` 链路组成的。这里以`service`为转接,拆分为`controller`-`service`,`service`-`repository`两段,
`repository`可以是`dao`或者是`rpc`,而这两组都不能独立成为一个应用,必须进行拼接才能形成一个完整的应用。于是如下:
- 当`repository`为`dao`时,两组对接形成 `controller`-`service`-`dao`,也就是我们常见的mvc模式。
- 当`repository`为`rpc`是,两组对接形成 `controller`-`service`-`rpc`,也就成了远程服务的调用模式。
基于这个原理,这里对项目进行了如下划分:
- shop-dependencies,全局依赖管理配置
- shop-controller,定义服务对外的接口,也是模块的入口,依赖shop-service进行功能的实现。
- shop-service,只定义了service接口,没有具体的实现功能,需要依赖`repository`
- shop-service-dao,依赖shop-service, 是shop-service的dao功能实现
- 可能会再次依赖别的模块的shop-service,如,下单就得用到了产品和会员,所以order-service-dao就会依赖到product-service和member-service(并不是product-service-dao和member-service-dao)
- ~~shop-service-rpc,依赖shop-service, 是shop-service的rpc功能实现~~
- ~~这里和shop-service-dao不太一样,rpc只依赖于自己模块下的shop-service,rpc会调用别的服务,也就是shop-controller~~
- shop-rpc-frame,shop-service的rpc封装,本项目集成了SpringCloud,使用了nacos,通过注解或者配置的方式实现服务与服务之间的通讯关系
- shop-common,全局通用工具封装
- shop-config,项目特性的全局配置
- shop-model,项目模型、实体、枚举、VO、常量等
- shop-dao,可分可不分的dao实现
- start-gateway,应用网关。
- start,启动项、模块的集合。
### 二、模块的组装
从上面得知,一个完整的mvc应用需要`controller`-`service`-`dao`的链路,所以,shop-controller就是我们模块的入口,shop-controller依赖了shop-service,
那么引入了shop-controller我们需要再引入一个shop-service-dao或者shop-service-rpc就可以了。
而start就是`controller`-`service`-`dao`的模块组装。
#### 1. 组装一个全功能的单体应用
start-all的pom下,我们把所有的shop-controller都引入进来,order-controller、member-controller、product-controller,
此时`controller`-`service`-`dao`链路是不完整的,然后再把 order-service-dao、member-service-dao、product-service-dao引入进来,
功能就齐全了。
```xml
com.mvc
order-controller
1.0-SNAPSHOT
com.mvc
member-controller
1.0-SNAPSHOT
com.mvc
product-controller
1.0-SNAPSHOT
com.mvc
order-service-dao
1.0-SNAPSHOT
com.mvc
member-service-dao
1.0-SNAPSHOT
com.mvc
product-service-dao
1.0-SNAPSHOT
```
启动start-all,就拥有了完整的下单,查看产品,扣减库存,查看会员信息的功能了。
- 下单接口`com.mvc.controller.order.OrderController.create`
- url路径:http://localhost:9000/shop/order/create
```json
{
"memId": "1",
"productId": "1",
"buyNum": "1"
}
```
#### 2.组装一个产品微服务
当我们上面的start-all的单体服务运行中,客流量突然增加,产品的访问数据剧增,此时我们需要对产品进行独立拆分和部署,
那么我们组装一个start-product服务,pom配置为:
```xml
com.mvc
product-controller
1.0-SNAPSHOT
com.mvc
product-service-dao
1.0-SNAPSHOT
```
启动start-product,查看产品接口。
+ 获得产品信息:`com.mvc.controller.product.ProductController.getProductById`
+ url路径: http://localhost:9001/shop/product/get?productId=1
#### 3.其它服务的组装
同理得,其它模块同样可以单独的服务化,见start-order,start-member。
进阶版[https://gitee.com/JayEinstein/archetype](https://gitee.com/JayEinstein/archetype)
### 三、启用RPC框架
相关模块:shop-rpc-frame,这里集成了SpringCloud和nacos。
> 如果本地没有安装nacos,需要修改bootstrap.yml,关闭nacos服务注册发现
```yaml
spring:
cloud:
nacos:
discovery:
# 开启nacos服务注册
enabled: true # 没有nacos的话,这里要改成false实现直连
namespace: ${spring.profiles.active}
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
```
前面的服务模块之间都是相互独立的,如果要产生联系,需要通过rpc进行配置。以下几个重要注解:
#### 1. @EnableRpcExport(服务提供方)
在上面那个例子中,start-product是要作为start-all的服务提供方,我们只需要配置start-product为服务提供者。
在启动项中配置上`@EnableRpcExport`注解,然后启动。
```java
@SpringBootApplication
@MapperScan("com.mvc.dao.mapper")
@ComponentScan(basePackages = {
"com.mvc.service"
})
// 开启RPC服务提供注解
@EnableRpcExport
public class ProductRunStarter {
public static void main(String[] args) {
SpringApplication.run(ProductRunStarter.class, args);
}
}
```
#### 2. @EnableRpcProxy (服务调用方)
start-all中我们是实现从原有产品的本地调用改为使用start-product的远程调用方式,我们需要在start-all的启动项上配置`@EnableRpcProxy`,表明调用关系。
```java
@SpringBootApplication
@MapperScan("com.mvc.dao.mapper")
@ComponentScan(basePackages = {
"com.mvc.controller",
"com.mvc.service",
"com.mvc.config"
})
// 远程服务调用配置
@EnableRpcProxy(serviceName = "start-product", value = "http://localhost:9001", proxyPackages = "com.mvc.service.product")
public class AllRunStarter {
public static void main(String[] args) {
SpringApplication.run(AllRunStarter.class, args);
}
}
```
再次启动start-all,重新调用下单接口,此时下单接口中所有关于shop-service-product模块的接口使用远程调用的方式在start-product中进行调用。
##### @EnableRpcProxy注解参数:
+ serviceName 服务名称,远程调用的服务名称,即spring.application.name。
+ enable 代理开关,false的时候不进行远程调用代理。
+ proxyPackages rpc代理路径,对路径下的service接口进行远程服务调用代理。
+ dynamicAddr 动态地址,为false的时候,rpc代理使用直连的方式。
+ host 直连地址,nacos功能关闭时或者dynamicAddr为false时使用的直接地址。
### 项目相关脚本
```sql
CREATE TABLE IF NOT EXISTS `shop_member` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`user_name` varchar(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE IF NOT EXISTS `shop_order` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`order_code` varchar(50) NULL DEFAULT NULL,
`product_id` int(0) NULL DEFAULT NULL,
`member_id` int(0) NULL DEFAULT NULL,
`code` varchar(50) NULL DEFAULT NULL,
`name` varchar(50) NULL DEFAULT NULL,
`price` decimal(11, 0) NULL DEFAULT NULL,
`status` varchar(20) NULL DEFAULT NULL,
`pay_amount` decimal(11, 0) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE IF NOT EXISTS `shop_product` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NULL DEFAULT NULL,
`code` varchar(50) NULL DEFAULT NULL,
`price` decimal(11, 0) NULL DEFAULT NULL,
`stock` int(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `shop_member` VALUES (1, '小明');
INSERT INTO `shop_product` VALUES (1, '苹果', 'A0001', 88, 10);
```
### 其他 - 目录结构变形划分
#### 项目结构划分一:(项目结构 - 业务模块)
```
archetype-mvc
+- shop-common
+- shop-controller
| +- member-controller
| +- order-controller
| +- product-controller
+- shop-dao
+- shop-model
+- shop-service
| +- member-service
| +- order-service
| +- product-service
+- shop-service-dao
| +- member-service-dao
| +- order-service-dao
| +- product-service-dao
+- shop-service-rpc
| +- member-service-rpc
| +- order-service-rpc
| +- product-service-rpc
+- start
| +- start-all
| +- start-member
| +- start-order
| +- start-product
+- start-gateway
| +- app-gateway
| +- bops-gateway
```
#### 项目结构划分二:(业务模块 - 项目结构)
```
archetype-mvc
+- shop-common
+- shop-member
| +- member-controller
| +- member-service
| +- member-service-dao
| +- member-service-rpc
+- shop-order
| +- order-controller
| +- order-service
| +- order-service-dao
| +- order-service-rpc
+- shop-product
| +- product-controller
| +- product-service
| +- product-service-dao
| +- product-service-rpc
+- shop-dao
+- shop-model
+- start
| +- start-all
| +- start-member
| +- start-order
| +- start-product
+- start-gateway
| +- app-gateway
| +- bops-gateway
```
#### 包结构
最终,项目的包结构应该与业务无关,而是通过对业务模块的导入丰富包目录下的内容。
```
com
+- example
+- project
+- start
+- controller
+- service
| +- dao
| +- rpc
+- dao
+- model
```