微服务架构设计笔记

从 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
跨模块注 MapperOrderService 注 CommodityMapper改注 CommodityFeign
跨模块写表OrderService 直接 update commodity 表走 Feign 调商品服务
共享 Entitycommon 里有 User.javaEntity 归位到各自服务
DTO/VO/Entity 混用Controller 直接返回 Entity用 VO 包装
Feign 返回 EntityR<Order> 暴露表结构返回 R<OrderDTO>
common 大杂烩业务工具类放 common移回各自服务
所有 Feign 混一起模块依赖了整个 api 包拆成独立 api 模块

六、总结

维度单体微服务
部署改一行,全量部署改一个服务,只部署一个
故障隔离一处崩溃,全局不可用服务隔离,互不影响
团队协作所有人改一个项目各团队独立维护
数据库所有表在一个库,随意 JOIN每个服务独立数据库,通过 Feign 通信
扩展性整个项目一起扩缩只扩高负载的服务
复杂度简单需要注册中心、配置中心、网关、链路追踪

微服务不是银弹。项目初期 3-5 人团队,业务简单,用单体反而更高效。微服务是随着业务增长、团队扩大后的自然演进结果。

更多推荐