# 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 ```