双模部署
MolanDev Cloud 的核心创新是双模部署架构:同一套代码可以在单体和微服务之间自由切换,业务代码零改动。
什么是双模部署
双模部署指同一套代码可以以单体模式或微服务模式部署运行,只需修改一行配置:
yaml
molandev:
run-mode: single # 单体模式
# run-mode: cloud # 微服务模式同一套代码
↓
┌───────────────┬───────────────┐
│ 单体模式 │ 微服务模式 │
│ (开发/小规模) │ (生产/大规模) │
└───────────────┴───────────────┘为什么这样设计
传统困境
开发企业级系统时,团队常常面临选择困难:
选择单体:
- ✅ 部署简单,运维成本低
- ✅ 开发快速,调试方便
- ✅ 资源占用少
- ❌ 业务增长后难以扩展
- ❌ 代码耦合,维护困难
- ❌ 无法独立部署模块
选择微服务:
- ✅ 服务独立,可独立扩展
- ✅ 技术栈灵活,故障隔离
- ✅ 团队可并行开发
- ❌ 架构复杂,初期成本高
- ❌ 运维困难,需要 DevOps
- ❌ 分布式事务、网络延迟
双模部署的优势
MolanDev Cloud 的解决方案是:为什么要选?我全都要!
- ✅ 初期快速开发:单体模式,极简部署,快速验证业务
- ✅ 后期平滑演进:业务增长后切换到微服务,无需重构代码
- ✅ 灵活切换:根据业务规模自由选择,不被架构绑架
- ✅ 降低风险:避免过度设计,也避免后期推倒重来
典型应用场景
场景一:创业公司
第 1 天 → 单体模式开发,本地调试,快速迭代
第 100 天 → 业务增长,修改配置切换微服务,拆分订单服务
第 365 天 → 持续演进,按需拆分支付、库存等服务优势: 初期不过度设计,业务增长后平滑演进。
场景二:内部管理系统
用户少、并发低 → 单体模式,一台服务器搞定
团队扩大后 → 按需拆分模块,不同团队负责不同服务优势: 根据实际负载选择架构,不盲目追求微服务。
场景三:学习与最佳实践
学习单体架构 → 理解分层架构、业务逻辑
学习微服务 → 理解服务拆分、分布式系统优势: 一套代码,两种架构,学习成本减半。
核心实现原理
双模部署的实现依赖三个核心设计:接口即服务、配置隔离、单体合并。
1. 接口即服务
核心思想: 所有服务间调用都通过 Feign 接口,不直接调用 Service。
java
// 定义 Feign 接口
@FeignClient(
name = "${molandev.service-name.molandev-base:molandev-base}",
path = "/feign/user"
)
public interface SysUserApi {
@GetMapping("/{id}")
UserDto getById(@PathVariable String id);
}
// 业务代码调用(单体/微服务完全一致)
@Service
public class OrderService {
@Autowired
private SysUserApi userApi; // 注入接口
public void createOrder(OrderDTO order) {
// 获取用户信息 - 单体本地调用,微服务HTTP调用
UserDto user = userApi.getById(order.getUserId());
}
}单体模式: Feign 接口通过 molandev-rpc 框架自动转为本地 Bean 调用
微服务模式: Feign 接口通过 HTTP 远程调用
为什么不用直接调用 Service?
| 方式 | 单体模式 | 微服务模式 | 切换成本 |
|---|---|---|---|
| 直接调用 Service | ✅ 可以 | ❌ 不行 | 需要重构代码 |
| Feign 接口 | ✅ 自动转本地 | ✅ HTTP 远程 | 零改动 |
2. 配置隔离
通过配置文件控制部署模式,不同模式使用不同的中间件配置。
单体模式配置:
yaml
molandev:
run-mode: single # 单体模式
lock:
type: memory # 使用内存锁
security:
mode: LOCAL # 本地认证模式
spring:
cloud:
nacos:
discovery:
enabled: false # 不需要注册中心微服务模式配置:
yaml
molandev:
run-mode: cloud # 微服务模式
security:
mode: CLOUD # 云端认证模式
spring:
application:
name: molandev-base
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 注册中心3. 事件驱动适配
java
// 发布事件(单体/微服务完全一致)
EventUtil.publish(new UserRegisteredEvent(user));
// 监听事件
@MolanListener
public void onUserRegistered(UserRegisteredEvent event) {
emailService.sendWelcomeEmail(event.getUser().getEmail());
}- 单体模式: 本地事件总线(Spring Event),进程内分发
- 微服务模式: RabbitMQ 分布式消息,跨服务通信
4. 定时任务双模
java
@TaskSchedule("NotifyExpiredUser")
public void execute() {
// 查询即将过期的用户
List<SysUserEntity> users = userService.findUsersSoonExpire();
for (SysUserEntity user : users) {
// 发送邮件提醒
msgSendApi.send(buildNotifyEvent(user));
}
}- 单体模式: 本地直接调用方法
- 微服务模式: HTTP 远程调度到对应服务
- 分布式锁: 多节点部署时防止重复执行
5. 单体合并
单体模式通过 molandev-standalone-service 模块启动,合并多个业务模块。
xml
<!-- molandev-standalone-service/pom.xml -->
<dependencies>
<!-- 引入基础服务,排除微服务依赖 -->
<dependency>
<groupId>com.molandev</groupId>
<artifactId>molandev-base</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入知识库服务 -->
<dependency>
<groupId>com.molandev</groupId>
<artifactId>molandev-knowledge</artifactId>
</dependency>
<!-- 引入 AI 修仙服务 -->
<dependency>
<groupId>com.molandev</groupId>
<artifactId>molandev-xiuxian</artifactId>
</dependency>
</dependencies>架构对比
| 特性 | 单体模式 | 微服务模式 |
|---|---|---|
| 部署形态 | 单个 JAR | 多个独立服务 |
| 接口调用 | 本地 Bean 注入 | HTTP 远程调用 |
| 事件通信 | 进程内内存分发 | RabbitMQ 消息队列 |
| 定时任务 | 本地直接调用 | HTTP 远程调度 |
| 分布式锁 | 内存锁 | Redis 分布式锁 |
| 配置管理 | 本地配置文件 | Nacos 集中管理 |
| 中间件 | MySQL + Redis | MySQL + Redis + RabbitMQ + Nacos |
| 启动时间 | ~10 秒 | ~1 分钟 |
| 内存占用 | ~500MB | ~1.5GB |
| 调试难度 | 简单 | 中等 |
| 水平扩展 | 不支持 | 支持 |
| 故障隔离 | 不支持 | 支持 |
| 运维复杂度 | 低 | 高 |
切换流程
从单体切换到微服务
修改配置
yamlmolandev: run-mode: single → cloud添加中间件
- 启动 RabbitMQ
- 启动 Nacos
- 导入配置文件到 Nacos
打包各个服务
bashcd molandev-backend mvn clean package -DskipTests部署各个服务
- 部署 Gateway
- 部署 molandev-base
- 部署 molandev-knowledge
- 部署 molandev-xiuxian
业务代码零改动! ✅
从微服务切换到单体
修改配置
yamlmolandev: run-mode: cloud → single使用 standalone 模块启动
bashcd molandev-standalone-service mvn spring-boot:run业务代码零改动! ✅
技术决策背后的思考
为什么选择 Feign 而不是 RPC 框架?
选择 Feign 的理由:
- Spring Cloud 原生支持,生态完善
- 声明式 HTTP 客户端,代码简洁
- 单体模式下可通过自定义 Client 转为本地调用
- 学习成本低,团队容易接受
不使用 gRPC/Dubbo 的原因:
- 增加额外学习成本
- 单体模式下优势不明显
- 对于企业级管理系统,HTTP 性能足够
为什么禁止数据库外键?
sql
-- ❌ 禁止:不要在数据库中配置外键约束
ALTER TABLE sys_user_role
ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES sys_user(id);
-- ✅ 正确:通过代码维护关联关系设计理由:
- 灵活性: 保持表结构灵活,便于后期扩展
- 性能: 避免级联操作影响性能
- 微服务友好: 微服务架构下无法使用跨库外键
- 代码可控: 关联关系由业务代码维护,逻辑更清晰
为什么不使用 XML 映射 SQL?
java
// ✅ 正确:使用 JDK 17 三引号 + @Select 注解
@Select("""
SELECT u.*
FROM sys_user u
INNER JOIN sys_user_role ur ON u.id = ur.user_id
WHERE ur.role_id = #{roleId}
""")
List<SysUserEntity> listByRoleId(@Param("roleId") String roleId);
// ❌ 禁止:使用 XML 文件设计理由:
- 减少文件数量: 无需维护 XML 文件
- SQL 更直观: 直接在看代码时看到 SQL
- JDK 17 三引号: 使多行 SQL 更易读
- 单表查询用 Wrapper: MyBatis Plus 的 LambdaQueryWrapper 更简洁
为什么网关不执行权限校验?
旧版本问题:
- 网关需要从各服务拉取路径与权限码的映射
- 服务上下线时需要刷新映射缓存
- 路径匹配规则复杂,容易出错
新架构优势:
- 网关只做认证和权限转发
- 后端通过 AOP 直接读取
@HasPermission注解 - 无需维护路径映射,职责清晰
- 扩展性更好,新增服务无需配置网关映射
最佳实践
开发环境使用单体
yaml
# application-local.yml
molandev:
run-mode: single优势:
- 快速启动,调试方便
- 无需启动多个中间件
- 断点调试简单
生产环境使用微服务
yaml
# application-prod.yml
molandev:
run-mode: cloud优势:
- 服务独立部署,可独立扩展
- 故障隔离,一个服务异常不影响其他
- 不同服务可使用不同技术栈
按需拆分
不要一开始就拆分所有服务,建议按以下顺序逐步拆分:
- 第一阶段: 保持单体,快速验证业务
- 第二阶段: 拆分负载高的模块(如文件服务)
- 第三阶段: 拆分业务独立的模块(如订单服务)
- 第四阶段: 按团队职责拆分
总结
MolanDev Cloud 的双模部署实现了:
- ✅ 一套代码: 单体/微服务自由切换,业务代码零改动
- ✅ 接口即服务: Feign 接口自动适配本地/远程调用
- ✅ 事件驱动: 统一事件总线,自动适配内存/MQ
- ✅ 灵活切换: 一行配置完成切换
- ✅ 降低风险: 避免过度设计,平滑演进
这是 MolanDev Cloud 的核心创新,也是区别于其他管理系统框架的最大优势。