微服务架构设计笔记
·
微服务架构设计笔记
从 Spring Boot 单体到 Spring Cloud Alibaba 微服务体系,一份通用的架构实战指南。
一、目录结构总览
project/
│
├── pom.xml # 父 POM:统一版本管理、模块聚合
│
├── project-framework/ # 【框架层】项目基础设施
│ ├── pom.xml
│ ├── project-common-core/ # 公共核心:统一响应、枚举基类、基础工具
│ ├── project-common-security/ # 安全模块:JWT 工具、加密工具、权限注解
│ ├── project-common-redis/ # 缓存模块:Redis 配置、分布式锁
│ └── project-api/ # Feign 接口层:所有服务间远程调用接口
│ ├── project-user-api/ # 用户服务 Feign 接口
│ ├── project-order-api/ # 订单服务 Feign 接口
│ └── project-commodity-api/ # 商品服务 Feign 接口
│
├── project-gateway/ # 【API 网关】统一入口、路由转发、鉴权、限流
│ ├── config/ # 跨域配置、路由配置
│ └── filter/ # JWT 鉴权过滤器、日志过滤器
│
├── project-services/ # 【业务服务层】聚合 POM,不写代码
│ ├── pom.xml
│ ├── project-user/ :8082 # 用户服务
│ ├── project-order/ :8083 # 订单服务
│ ├── project-commodity/ :8084 # 商品服务
│ └── project-message/ :8085 # 消息服务
│
├── project-job/ # 【定时任务】XXL-Job 执行器
└── docker-compose.yml # 本地开发环境
二、为什么这样设计
2.1 分层思想
框架层(framework)
↓ 被依赖
业务服务层(services)
↑ 调用
API 层(api)── Feign ──→ 业务服务层
↓
网关层(gateway)── 路由 ──→ 业务服务层
每一层职责清晰:
| 层 | 职责 | 能依赖谁 |
|---|---|---|
| framework | 提供基础能力 | 只依赖第三方库 |
| api | 定义服务间契约 | 依赖 framework |
| services | 业务逻辑实现 | 依赖 framework + api |
| gateway | 流量入口 | 依赖 framework |
2.2 对比单体架构
单体架构:
项目/
├── src/main/java/com/xxx/
│ ├── controller/ # 所有 Controller 混在一起
│ ├── service/ # 所有 Service 混在一起
│ ├── mapper/ # 所有 Mapper 混在一起
│ ├── entity/ # 所有实体类
│ └── utils/ # 所有工具类
└── pom.xml # 所有依赖
问题:
- 改一行代码,整个项目重新部署
- 订单模块挂了,用户模块也用不了
- 所有 Service 可以互相注入,耦合无法控制
- 几十个人同时改一个项目,Git 冲突不断
微服务架构:
project/
├── project-framework/ # 公共部分独立
├── project-services/
│ ├── project-user/ # 独立部署
│ └── project-order/ # 独立部署
└── project-gateway/ # 统一入口
好处:
- 改用户服务,只重启用户服务
- 订单服务挂了,用户还能登录
- 服务和服务的通信必须通过 Feign 接口,能调什么、不能调什么一清二楚
- 每个团队只维护自己的服务
三、单体 → 微服务改造详解
3.1 Controller 层改造
改造前(单体):
// 所有业务逻辑堆在 Controller 里
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderMapper orderMapper; // 直接注 Mapper
@Autowired
private CommodityMapper commodityMapper; // 跨模块直接注 Mapper
@PostMapping("/create")
public Result createOrder(@RequestBody OrderCreateDTO dto) {
// 1. 查库存(直接调商品 Mapper)
Commodity commodity = commodityMapper.selectById(dto.getCommodityId());
if (commodity.getAmount() <= 0) {
return Result.fail("库存不足");
}
// 2. 减库存(直接写商品表)
commodity.setAmount(commodity.getAmount() - 1);
commodityMapper.updateById(commodity);
// 3. 查用户(直接调用户 Mapper)
User user = userMapper.selectById(dto.getUserId());
if (user.getCoinBalance() < dto.getPrice()) {
return Result.fail("余额不足");
}
// 4. 扣余额(直接写用户表)
user.setCoinBalance(user.getCoinBalance() - dto.getPrice());
userMapper.updateById(user);
// 5. 保存订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setCommodityId(dto.getCommodityId());
order.setAmount(dto.getPrice());
orderMapper.insert(order);
return Result.success(order);
}
}
问题: 一个下单操作,直接跨了 3 个数据库表,没有任何边界。哪天有人改了商品表字段,订单模块直接崩。
改造后(微服务):
// 订单服务 —— 只调自己的 Service,跨服务走 Feign
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService; // 自己的 Service
@PostMapping("/create")
public Result<OrderVO> create(@RequestBody OrderCreateDTO dto) {
return Result.success(orderService.createOrder(dto));
}
}
// 订单服务 —— Service 层做编排
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper; // 自己模块的 Mapper
@Autowired
private CommodityFeign commodityFeign; // Feign 调商品服务
@Autowired
private UserFeign userFeign; // Feign 调用户服务
@Transactional
public OrderVO createOrder(OrderCreateDTO dto) {
// 1. 远程查商品信息(调用商品服务)
R<CommodityDTO> r = commodityFeign.getById(dto.getCommodityId());
CommodityDTO commodity = r.getData();
if (commodity.getStock() <= 0) {
throw new BusinessException("库存不足");
}
// 2. 远程减库存(调用商品服务)
commodityFeign.deductStock(dto.getCommodityId(), 1);
// 3. 远程扣款(调用用户服务)
userFeign.deductBalance(dto.getUserId(), dto.getPrice());
// 4. 保存订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setCommodityId(dto.getCommodityId());
orderMapper.insert(order);
return OrderVO.from(order);
}
}
核心变化:
@Autowired CommodityMapper→@Autowired CommodityFeign(不再直接操作别人的数据库)- 跨模块调用从"本地方法调用"变成"远程 HTTP 调用"
- 每个服务只管自己的数据库
3.2 Feign 接口设计
API 模块集中管理:
// 放在 project-api/project-commodity-api 中(不是商品服务的实现模块)
// 这样订单服务只依赖 api,不依赖商品服务的整个实现
@FeignClient(name = "project-commodity", path = "/commodity")
public interface CommodityFeign {
@GetMapping("/{id}")
R<CommodityDTO> getById(@PathVariable("id") Long id);
@PutMapping("/stock/deduct")
R<Void> deductStock(@RequestParam("id") Long id,
@RequestParam("count") Integer count);
}
// 商品服务实现 Feign 对应的 Controller
@RestController
@RequestMapping("/commodity")
public class CommodityFeignController {
@Autowired
private CommodityService commodityService;
@GetMapping("/{id}")
public R<CommodityDTO> getById(@PathVariable("id") Long id) {
Commodity entity = commodityService.getById(id);
// Entity → DTO,不暴露数据库表结构
CommodityDTO dto = new CommodityDTO();
dto.setId(entity.getId());
dto.setName(entity.getName());
dto.setPrice(entity.getPrice());
dto.setStock(entity.getAmount() - entity.getSoldCount());
return R.ok(dto);
}
@PutMapping("/stock/deduct")
public R<Void> deductStock(@RequestParam("id") Long id,
@RequestParam("count") Integer count) {
commodityService.deductStock(id, count);
return R.ok();
}
}
为什么 Feign 要独立成 api 模块:
❌ 错误做法:
订单服务的 pom.xml 里直接依赖商品服务的实现模块
→ 订单服务能访问商品服务的所有类,耦合失控
✅ 正确做法:
订单服务只依赖 commodity-api(只有 Feign 接口 + DTO 定义)
→ 只能通过 Feign 接口调用,边界清晰
3.3 公共模块拆分
改造前(大杂烩 common):
// 所有东西扔在一个 common 模块里
com.xxx.common/
├── result/R.java # 统一响应
├── exception/BizException.java # 业务异常
├── utils/
│ ├── JwtUtil.java # JWT 工具
│ ├── RedisUtil.java # Redis 工具
│ ├── OrderUtil.java # ❌ 订单业务工具,不应该在 common
│ └── CommodityUtil.java # ❌ 商品业务工具,不应该在 common
├── entity/
│ ├── User.java # ❌ 不应该共享实体
│ └── Order.java # ❌ 不应该共享实体
└── dto/ # 所有 DTO 混在一起
改造后(按职责拆):
project-framework/
├── project-common-core/ # 只放真正通用的东西
│ └── com.xxx.framework.common/
│ ├── result/R.java # 统一响应
│ ├── exception/ # 基础异常
│ ├── enums/BaseEnum.java # 枚举基类
│ └── utils/IdWorker.java # 真正的基础工具
│
├── project-common-security/ # 安全相关独立
│ └── com.xxx.framework.security/
│ ├── JwtUtil.java
│ └── SecurityConfig.java
│
└── project-api/ # Feign 接口独立
└── com.xxx.api.feign/
├── CommodityFeign.java
└── OrderFeign.java
判断标准:
| 能不能放 common | 示例 |
|---|---|
| ✅ 所有服务都用到 | R.java、基础异常 |
| ✅ 与业务无关 | 雪花算法 ID 生成器 |
| ❌ 只有一两个服务用到 | OrderUtil |
| ❌ 包含业务逻辑 | 价格计算工具 |
| ❌ 数据库实体 | User.java、Order.java |
四、完整微服务内部结构
每个微服务内部采用分层架构:
project-user/
├── pom.xml
└── src/main/java/com/xxx/user/
│
├── controller/ # 接口层:接收请求、参数校验
│ ├── UserController.java # 给前端用的接口
│ └── UserFeignController.java # 给其他服务 Feign 调用的接口
│
├── service/ # 业务层:核心逻辑、事务管理
│ ├── UserService.java
│ └── impl/
│ └── UserServiceImpl.java
│
├── mapper/ # 数据访问层
│ └── UserMapper.java
│
├── domain/ # 领域模型
│ ├── User.java # 数据库实体
│ └── enums/
│ └── UserStatusEnum.java
│
├── dto/ # 数据传输对象
│ ├── request/
│ │ └── UserRegisterReq.java # 入参
│ └── response/
│ └── UserInfoResp.java # 出参
│
├── infrastructure/ # 基础设施(外部服务调用)
│ └── OrderFeignClient.java # 封装对订单服务的 Feign 调用
│
└── config/ # 本服务专属配置
└── MyBatisConfig.java
五、改造检查清单
从单体改微服务,逐个排查:
| 检查项 | 问题表现 | 怎么改 |
|---|---|---|
| Controller 直接注 Mapper | @Autowired private UserMapper | 改注 Service |
| 跨模块注 Mapper | OrderService 注 CommodityMapper | 改注 CommodityFeign |
| 跨模块写表 | OrderService 直接 update commodity 表 | 走 Feign 调商品服务 |
| 共享 Entity | common 里有 User.java | Entity 归位到各自服务 |
| DTO/VO/Entity 混用 | Controller 直接返回 Entity | 用 VO 包装 |
| Feign 返回 Entity | R<Order> 暴露表结构 | 返回 R<OrderDTO> |
| common 大杂烩 | 业务工具类放 common | 移回各自服务 |
| 所有 Feign 混一起 | 模块依赖了整个 api 包 | 拆成独立 api 模块 |
六、总结
| 维度 | 单体 | 微服务 |
|---|---|---|
| 部署 | 改一行,全量部署 | 改一个服务,只部署一个 |
| 故障隔离 | 一处崩溃,全局不可用 | 服务隔离,互不影响 |
| 团队协作 | 所有人改一个项目 | 各团队独立维护 |
| 数据库 | 所有表在一个库,随意 JOIN | 每个服务独立数据库,通过 Feign 通信 |
| 扩展性 | 整个项目一起扩缩 | 只扩高负载的服务 |
| 复杂度 | 简单 | 需要注册中心、配置中心、网关、链路追踪 |
微服务不是银弹。项目初期 3-5 人团队,业务简单,用单体反而更高效。微服务是随着业务增长、团队扩大后的自然演进结果。
更多推荐

所有评论(0)