跳至主要內容

微服务拆分实操:用什么原则划边界

郑天祺大约 18 分钟架构设计微服务架构服务拆分DDD分布式

前言

"微服务"这三个字在技术圈已经热了十多年,但"怎么拆"始终是最困扰架构师的问题。拆得太粗——换了个壳的单体;拆得太细——运维成本爆炸,调试像噩梦。

本文不讲微服务的基础概念(假设你已经知道什么是微服务),聚焦于一个核心问题:面对一个单体系统,你该用什么原则来划边界?


第一部分:单体架构的痛点与拆分的时机

1.1 单体不是原罪

在谈拆分之前,必须明确一个观点:单体架构不是原罪。很多成功的系统——包括 GitHub 早期、Stack Overflow——都是单体架构。单体的问题不在于"它是单体",而在于"它何时变得不可维护"。

一个设计良好的模块化单体,比一个拆分混乱的微服务系统好一万倍:

// 设计良好的模块化单体
com.company.app
├── order          // 订单模块 - 内部高内聚
│   ├── api        //   对外接口
│   └── internal   //   内部实现(包级私有)
├── inventory      // 库存模块
├── payment        // 支付模块
└── user           // 用户模块
// 模块间通过接口通信,互不依赖内部实现

1.2 单体的真正痛点

当你开始经历以下问题时,拆分才变得必要:

痛点一:协作冲突

场景:20 个开发者在同一个代码仓库工作

周一:张三改了 OrderService.java 的 calculateTotal() 方法
周二:李四也改了同一个文件,合并冲突
周三:张三的改动导致李四的单元测试失败
周四:王五的 feature 被张三和李四的改动 block 了
周五:发布日,大家都不敢合并代码了

痛点二:部署耦合

场景:订单模块的小改动需要全量部署

14:00 - 订单模块修了一个 bug,改了 3 行代码
14:05 - 提交代码,触发 CI/CD
14:30 - 构建完成(编译了全部 500 个类)
14:45 - 全量回归测试(跑了 3000 个测试用例)
15:00 - 支付模块的测试挂了!有人上午改支付导致了回归
15:30 - 修复支付模块的回归问题
16:30 - 重新构建、重新测试
17:00 - 终于上线了

—— 改 3 行代码,花了 3 小时上线

痛点三:扩展瓶颈

场景:大促期间,订单服务 CPU 飙到 95%,但库存服务只用 10%

单体架构中你只能整体扩容:
┌─────────────────────────────────┐
│  单体应用 × 5 实例              │
│  ┌─────┐┌─────┐┌─────┐┌─────┐ │
│  │订单 ││库存 ││支付 ││用户 │ │
│  │95%  ││10%  ││15%  ││5%   │ │
│  └─────┘└─────┘└─────┘└─────┘ │
└─────────────────────────────────┘
资源大量浪费!你只能把不忙的服务也一起扩

微服务架构中:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│订单  │ │库存  │ │支付  │ │用户  │
│×10   │ │×2    │ │×3    │ │×2    │
└──────┘ └──────┘ └──────┘ └──────┘
按需扩容,资源高效利用

痛点四:技术栈锁定

单体项目一旦选定了技术栈(Java 8 + Spring Boot 2.x + MyBatis),就很难在某一个模块升级到 Java 21 或切换到 JPA。微服务允许每个服务独立选择技术栈。

1.3 拆分的真正时机

信号说明
团队规模 > 10 人,多个团队在同一代码库工作协作冲突频繁
不同模块有明显的差异化扩展需求资源浪费严重
不同模块有不同的变更节奏部署耦合导致上线缓慢
某个模块需要独立的技术栈或语言技术受限
系统的某个部分需要独立的合规/安全级别安全隔离需求

如果你只命中了一条,用模块化单体就够了,不需要拆微服务。如果你命中了三条以上,拆分才有切实的 ROI。


第二部分:拆分原则——业务边界优先

2.1 核心原则:先业务边界,再数据边界

微服务拆分的两颗"北极星":

原则 1:业务边界优先——按照业务能力划分,而不是按技术层次划分。

// ❌ 错误:按技术层次拆分
com.company.app
├── controller-service    // 所有 Controller → 一个服务
├── business-service      // 所有 Service → 一个服务
└── data-service          // 所有 DAO → 一个服务
// 问题:一个业务请求要穿透三层服务,延迟高、耦合重

// ✅ 正确:按业务边界拆分
com.company.app
├── order-service      // 订单相关的 Controller + Service + DAO 在一起
├── inventory-service  // 库存相关的在一起
└── payment-service    // 支付相关的在一起
// 每个服务自包含,独立部署

原则 2:数据边界优先——每个微服务拥有自己的数据库,服务间不共享数据库。

// ❌ 错误:多个微服务共享一个数据库
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ 订单服务     │  │ 库存服务     │  │ 支付服务     │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       └─────────────────┼─────────────────┘
                         ▼
              ┌─────────────────────┐
              │   共享数据库        │
              │   (单点故障!)       │
              └─────────────────────┘
// 问题:库存服务改了表结构,订单服务挂了

// ✅ 正确:每个服务独立数据库
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ 订单服务     │  │ 库存服务     │  │ 支付服务     │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       ▼                 ▼                 ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ 订单DB       │  │ 库存DB       │  │ 支付DB       │
└──────────────┘  └──────────────┘  └──────────────┘

2.2 限界上下文到微服务的映射

DDD 的限界上下文是最自然的微服务边界候选:

担保业务的限界上下文 → 微服务映射:

限界上下文          微服务              核心职责
─────────────────────────────────────────────────
客户管理    →     customer-service    客户信息、信用评级
授信管理    →     credit-service       额度申请、额度管理
担保管理    →     guarantee-service    担保申请、合同管理
风控评估    →     risk-service          风控规则、风险评估
签约管理    →     contract-service      电子合同、签署流程
财务管理    →     finance-service       放款、还款、账务

但是注意:限界上下文 ≠ 微服务!一个限界上下文可以包含多个微服务,多个小型的限界上下文也可以合并为一个微服务。关键是看团队组织变更频率

2.3 三种常见拆分策略

策略一:按业务能力拆分(最常用)

这是最常见也是最推荐的拆分方式。每个微服务对应一个核心业务能力。

电商系统的业务能力拆分:

用户能力    → user-service        (注册、登录、认证、个人信息)
商品能力    → product-service     (商品发布、类目、搜索)
订单能力    → order-service       (下单、订单管理)
库存能力    → inventory-service   (库存扣减、补货)
支付能力    → payment-service     (支付、退款)
物流能力    → logistics-service   (发货、物流追踪)
营销能力    → marketing-service   (优惠券、促销活动)

策略二:按子域拆分(DDD 视角)

按照 DDD 中的核心域、支撑域、通用域划分:

核心域(公司核心竞争力,重点投入)
├── 风控引擎服务    (risk-engine)    — 自研,持续优化
└── 担保交易服务    (guarantee-core) — 自研,核心业务

支撑域(支撑核心业务,但不是差异化所在)
├── 客户管理服务    (customer)
├── 合同管理服务    (contract)
└── 产品配置服务    (product-config)

通用域(通用能力,可采购/复用)
├── 认证授权服务    (auth)          — 可采购第三方
├── 消息通知服务    (notification)
└── 文件存储服务    (file-storage)  — 用 OSS/COS

核心域投入最强资源,通用域能买就买——这是最有 ROI 的资源分配策略。

策略三:按团队拆分(康威定律)

康威定律说:系统架构反映了组织的沟通结构。如果你有 3 个团队,大概率需要 3 个服务(或 3 的倍数)。

团队 A → order-service + payment-service     (交易域)
团队 B → product-service + inventory-service  (商品域)
团队 C → user-service + marketing-service     (用户域)

一个团队负责的服务不要太多(3-5 个为宜),否则认知负荷太大。


第三部分:数据库拆分——最难的一步

3.1 数据库拆分的三个级别

Level 1: 共享数据库
┌─────┐ ┌─────┐ ┌─────┐
│ Svc1│ │ Svc2│ │ Svc3│
└──┬──┘ └──┬──┘ └──┬──┘
   └───────┼───────┘
           ▼
    ┌─────────────┐
    │  Shared DB  │
    └─────────────┘
特点:最简单,但有单点故障和服务间隐式耦合

Level 2: 共享数据库独立 Schema
┌─────┐ ┌─────┐ ┌─────┐
│ Svc1│ │ Svc2│ │ Svc3│
└──┬──┘ └──┬──┘ └──┬──┘
   │       │       │
   ▼       ▼       ▼
┌─────┐ ┌─────┐ ┌─────┐
│sch1 │ │sch2 │ │sch3 │
└──┬──┘ └──┬──┘ └──┬──┘
   └───────┼───────┘
           ▼
    ┌─────────────┐
    │  Shared DB  │ (同一个数据库实例)
    └─────────────┘
特点:逻辑隔离,物理共享;迁移的中间状态

Level 3: 独立数据库
┌─────┐ ┌─────┐ ┌─────┐
│ Svc1│ │ Svc2│ │ Svc3│
└──┬──┘ └──┬──┘ └──┬──┘
   ▼       ▼       ▼
┌─────┐ ┌─────┐ ┌─────┐
│ DB1 │ │ DB2 │ │ DB3 │
└─────┘ └─────┘ └─────┘
特点:完全隔离,独立扩展,微服务目标状态

推荐路线图:Level 1 → Level 2 → Level 3,不要一步到位。Level 2 是安全的中间状态。

3.2 数据一致性的挑战

拆分数据库最大的挑战是:以前一个事务搞定的操作,现在跨越了多个数据库

// 单体中:一个事务搞定
@Transactional
public void placeOrder(OrderRequest request) {
    // 1. 扣减库存
    inventoryMapper.decrease(request.getProductId(), request.getQuantity());
    // 2. 创建订单
    orderMapper.insert(createOrderDO(request));
    // 3. 扣减余额
    paymentMapper.deduct(request.getUserId(), request.getAmount());
}
// 三步全在一个数据库事务中,要么全成,要么全败

// 微服务中:三个独立操作
// 问题:库存扣了,订单创建失败了怎么办?

解决方案矩阵

方案一致性复杂度适用场景
Saga 编排最终一致长事务,跨多个服务
TCC (Try-Confirm-Cancel)强一致资金类操作
本地消息表最终一致异步解耦
事件驱动 + 补偿最终一致复杂业务流程
二阶段提交 (2PC)强一致不推荐——性能差、单点故障

推荐:Saga 编排模式

/**
 * 订单 Saga 编排器
 * 每个步骤有对应的补偿操作
 */
@Service
public class OrderSagaOrchestrator {

    @Autowired private InventoryClient inventoryClient;
    @Autowired private OrderService orderService;
    @Autowired private PaymentClient paymentClient;

    public OrderResult execute(OrderRequest request) {
        // Step 1: 预留库存
        ReserveInventoryResult reserveResult =
            inventoryClient.reserve(request.getProductId(), request.getQuantity());
        if (!reserveResult.isSuccess()) {
            return OrderResult.fail("库存不足");
        }

        try {
            // Step 2: 创建订单
            Order order = orderService.create(request);
            if (order == null) {
                throw new SagaCompensationException("订单创建失败");
            }

            // Step 3: 扣款
            PaymentResult paymentResult =
                paymentClient.deduct(request.getUserId(), request.getAmount());
            if (!paymentResult.isSuccess()) {
                throw new SagaCompensationException("扣款失败");
            }

            // Step 4: 确认库存
            inventoryClient.confirm(reserveResult.getReserveId());

            return OrderResult.success(order.getId());

        } catch (SagaCompensationException e) {
            // 补偿:释放预留的库存
            inventoryClient.cancel(reserveResult.getReserveId());
            return OrderResult.fail(e.getMessage());
        }
    }
}

第四部分:实战——电商系统拆分全过程

4.1 现状分析

假设我们有一个电商单体应用,包含以下功能:

Monolith Application
├── 用户管理 (注册、登录、个人信息)
├── 商品管理 (发布、上下架、搜索)
├── 购物车
├── 订单管理 (下单、支付、退款)
├── 库存管理 (扣减、回补、预警)
├── 物流管理 (发货、签收、退回)
├── 营销管理 (优惠券、满减、限时购)
└── 后台管理 (数据报表、权限管理)

团队情况:3 个团队,共 15 人。

4.2 拆分方案设计

第一步:识别业务边界

画出业务能力图谱:

┌────────────────────────────────────────────────────┐
│                    电商系统                          │
│                                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ 用户域   │  │ 商品域   │  │ 交易域   │        │
│  │          │  │          │  │          │        │
│  │·用户注册 │  │·商品管理 │  │·购物车   │        │
│  │·认证登录 │  │·类目管理 │  │·下单     │        │
│  │·地址管理 │  │·库存管理 │  │·支付     │        │
│  │·会员等级 │  │·搜索     │  │·退款     │        │
│  └──────────┘  └──────────┘  └──────────┘        │
│                                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ 物流域   │  │ 营销域   │  │ 数据域   │        │
│  │          │  │          │  │          │        │
│  │·发货管理 │  │·优惠券   │  │·运营报表 │        │
│  │·物流追踪 │  │·满减活动 │  │·用户分析 │        │
│  │·签收确认 │  │·限时购   │  │·财务统计 │        │
│  └──────────┘  └──────────┘  └──────────┘        │
└────────────────────────────────────────────────────┘

第二步:映射到微服务

团队 A (交易域)       团队 B (商品域)        团队 C (平台域)
├── order-service     ├── product-service     ├── user-service
├── payment-service   ├── inventory-service   ├── marketing-service
└── cart-service      └── search-service      └── data-service

物流域 → logistics-service (由团队 A 兼管)

第三步:确定数据库拆分方案

服务                    数据库              关键表
────────────────────────────────────────────────────
user-service         user_db             t_user, t_address, t_member
product-service      product_db          t_product, t_category, t_sku
inventory-service    inventory_db        t_inventory, t_stock_log
order-service        order_db            t_order, t_order_item
payment-service      payment_db          t_payment, t_refund
cart-service         cart_db(is Redis)   cart:{userId}
marketing-service    marketing_db        t_coupon, t_promotion
logistics-service    logistics_db        t_shipment, t_tracking

4.3 跨服务数据访问处理

拆分后,订单服务需要"用户的收货地址"——但地址在 user-service 中。如何处理?

// 方案 A:API 调用(推荐用于实时数据)
@Service
public class OrderApplicationService {

    @Autowired private UserClient userClient;
    @Autowired private OrderRepository orderRepository;

    public OrderDetailDTO getOrderDetail(Long orderId) {
        Order order = orderRepository.findById(orderId);

        // 跨服务调用获取用户地址
        UserAddress address = userClient.getAddress(
            order.getUserId(), order.getAddressId());

        return OrderDetailDTO.from(order, address);
    }
}

// 方案 B:数据冗余(推荐用于高频读取)
// 下单时将地址快照保存在订单中
@TableName("t_order")
public class OrderDO {
    private Long id;
    private Long userId;
    // 地址快照 - 冗余存储,不随用户修改地址而变
    private String snapshotReceiverName;
    private String snapshotPhone;
    private String snapshotProvince;
    private String snapshotCity;
    private String snapshotDetailAddress;
    // ...
}
// 原则:订单的收货地址是"下单时的快照",不是"用户当前的地址"

数据访问策略决策树

需要其他服务的数据?
  ├── 是实时数据(如库存余量)?
  │   └── API 调用
  ├── 是历史快照(如下单时的地址)?
  │   └── 数据冗余存储
  ├── 是高频读取但低频变更(如商品名称)?
  │   └── 本地缓存 + 变更通知
  └── 是报表/分析场景?
      └── 数据中台/数据仓库

4.4 拆分执行计划

不要一次性拆完!推荐分阶段迁移:

Phase 1 (第 1-2 月): 基础设施准备
  ├── 搭建服务注册中心 (Nacos/Eureka)
  ├── 搭建配置中心 (Nacos/Apollo)
  ├── 搭建 API 网关 (Gateway/Kong)
  ├── 搭建统一监控 (Prometheus + Grafana)
  └── 制定接口规范 + 错误码规范

Phase 2 (第 3-4 月): 拆分非核心服务(先易后难)
  ├── 拆分 user-service
  ├── 拆分 marketing-service
  └── 拆分 logistics-service

Phase 3 (第 5-6 月): 拆分核心服务
  ├── 拆分 product-service
  ├── 拆分 inventory-service
  └── 拆分 order-service

Phase 4 (第 7 月): 干掉单体
  ├── 下线单体中的已迁移功能
  └── 清理代码和数据库

先拆"外围"再拆"核心",风险最小。如果非核心服务的拆分出了问题,至少核心业务还在单体中稳定运行。


第五部分:拆分后的挑战

5.1 分布式事务

拆分后,以前一个 @Transactional 搞定的事,现在需要分布式事务处理。

// 场景:下单 = 扣库存 + 创建订单 + 使用优惠券 + 扣款
// 涉及 4 个服务!如何保证一致性?

/**
 * 使用 Seata AT 模式处理分布式事务
 */
@GlobalTransactional  // Seata 全局事务注解
public OrderResult placeOrder(OrderRequest request) {
    // 1. 锁定库存(inventory-service)
    inventoryClient.lock(request.getItems());

    // 2. 使用优惠券(marketing-service)
    couponClient.use(request.getCouponId());

    // 3. 创建订单(order-service,本服务)
    Order order = orderRepository.save(createOrder(request));

    // 4. 发起支付(payment-service)
    paymentClient.pay(order.getId(), order.getTotalAmount());

    return OrderResult.success(order);
}
// Seata 自动管理各服务的本地事务,实现整体一致性

/**
 * 使用 RocketMQ 事务消息(更轻量,推荐)
 */
@Service
public class OrderService {

    @Autowired private RocketMQTemplate rocketMQTemplate;

    @Transactional
    public void createOrder(OrderRequest request) {
        // Step 1: 本地事务中创建订单
        Order order = orderRepository.save(createOrderEntity(request));

        // Step 2: 发送半消息(事务消息)
        rocketMQTemplate.sendMessageInTransaction(
            "order-created-topic",
            MessageBuilder.withPayload(new OrderCreatedEvent(order)).build(),
            order.getId()  // 事务参数
        );
        // 如果本地事务提交,消息才真正发送
        // 如果本地事务回滚,消息被丢弃
    }
}

// 库存服务消费消息
@RocketMQMessageListener(topic = "order-created-topic", consumerGroup = "inventory-group")
@Component
public class InventoryDeductListener
        implements RocketMQListener<OrderCreatedEvent> {

    @Override
    @Transactional
    public void onMessage(OrderCreatedEvent event) {
        // 扣减库存
        inventoryRepository.deduct(
            event.getProductId(), event.getQuantity());
    }
}

5.2 服务发现与配置管理

# Nacos 配置中心 - order-service 配置示例
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: nacos-cluster:8848
        namespace: production
        group: ECOMMERCE
      config:
        server-addr: nacos-cluster:8848
        namespace: production
        group: ECOMMERCE
        file-extension: yaml
        shared-configs:
          - data-id: common-config.yaml
            group: ECOMMERCE
            refresh: true
// 服务间调用 - 使用 Feign + Nacos 服务发现
@FeignClient(name = "inventory-service")  // Nacos 中注册的服务名
public interface InventoryClient {

    @PostMapping("/api/inventory/lock")
    Result<LockResult> lock(@RequestBody LockRequest request);

    @PostMapping("/api/inventory/confirm/{reserveId}")
    Result<Void> confirm(@PathVariable String reserveId);

    @PostMapping("/api/inventory/cancel/{reserveId}")
    Result<Void> cancel(@PathVariable String reserveId);
}

// 使用时自动负载均衡到 inventory-service 的多个实例
@Autowired private InventoryClient inventoryClient;

5.3 监控与链路追踪

// 使用 Micrometer + OpenTelemetry 做全链路追踪
@RestController
public class OrderController {

    @Autowired private MeterRegistry meterRegistry;

    @PostMapping("/orders")
    public Result<OrderResponse> createOrder(@RequestBody OrderRequest request) {
        // 自定义指标
        Timer.Sample sample = Timer.start(meterRegistry);

        try {
            OrderResponse response = orderService.create(request);

            // 记录成功指标
            meterRegistry.counter("orders.created.total").increment();
            meterRegistry.counter("orders.amount.total")
                .increment(response.getTotalAmount().doubleValue());

            return Result.success(response);

        } catch (Exception e) {
            meterRegistry.counter("orders.created.error").increment();
            throw e;

        } finally {
            sample.stop(Timer.builder("orders.create.duration")
                .description("订单创建耗时")
                .register(meterRegistry));
        }
    }
}

5.4 API 网关

# Spring Cloud Gateway 路由配置
spring:
  cloud:
    gateway:
      routes:
        # 订单服务路由
        - id: order-service
          uri: lb://order-service  # lb:// = 负载均衡
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=0
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200

        # 商品服务路由
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**
          filters:
            - name: CircuitBreaker
              args:
                name: productCircuitBreaker
                fallbackUri: forward:/fallback/products

      # 全局过滤器
      default-filters:
        - AddRequestHeader=X-Request-Id, ${random.uuid}
        - name: Retry
          args:
            retries: 2
            statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE

第六部分:拆得太碎的代价

6.1 "微服务过小症"的症状

当微服务拆得过细时,会出现以下问题:

症状一:调试地狱

场景:排查一个订单创建失败的问题

调试路径:
  API Gateway → order-service → inventory-service → product-service
                                                     → payment-service
                       → marketing-service (优惠券)
                       → user-service (收货地址)

一个请求跨越 6 个服务,你需要:
  - 登录 6 台机器/容器看日志
  - 在 6 个代码仓库里找代码
  - 追踪 Jaeger 中 6 个 span 之间的关系
  - 在 Kafka 里找中间消息

症状二:运维爆炸

N 个微服务的运维成本:
  - N 个代码仓库
  - N 条 CI/CD 流水线
  - N 个 Dockerfile / Helm Chart
  - N 组配置(开发/测试/生产)
  - N 个数据库实例
  - N × 2 个容器实例(至少)
  - N 个监控告警规则

当 N = 50 时:
  50 个代码仓库 × 3 分支 × 3 环境 = 450 个部署单元
  运维团队已经哭晕在机房

症状三:网络延迟累加

单体:查询订单详情 → 1 次数据库查询,耗时 5ms

微服务(过细拆分后):
  order-service 查订单        → 5ms
  user-service 查用户信息      → 10ms (RPC)
  product-service 查商品信息   → 15ms (RPC)
  payment-service 查支付状态   → 12ms (RPC)
  logistics-service 查物流     → 8ms  (RPC)
  ─────────────────────────────
  总耗时:50ms(是单体的 10 倍!)

6.2 合理的服务粒度

一个简单的判断标准:一个微服务 = 一个 2-Pizza 团队(6-8 人)可以维护的范围

其他参考指标:

维度太细刚好太粗
代码行数< 10005000-50000> 200000
数据表数量< 510-30> 100
API 接口数< 510-50> 200
负责团队人数< 23-8> 15
独立部署频率每天多次每天/每周每月
启动时间< 3 秒5-30 秒> 2 分钟

6.3 什么时候该合并

如果你发现以下情况,说明拆过了,该合并:

  1. A 服务和 B 服务总是同时变更、同时发布 → 它们应该是一个服务
  2. A 服务崩溃了,B 服务完全不能用(强运行时耦合) → 它们应该在一个进程里
  3. A 服务和 B 服务共享大量代码(复制粘贴) → 应该抽取公共库,或者合并
  4. 一个简单功能要在 3 个以上仓库里改代码 → 边界划错了
// 合并过度拆分的服务
// 如果 order-processing-service 和 order-query-service
// 共享同样的数据库、同样的实体定义、总是同时发布
// → 它们应该是一个 order-service

第七部分:拆分决策框架

7.1 微服务拆分决策树

┌──────────────────────────────────────┐
│ 当前是否是拆分的好时机?              │
├──────────────────────────────────────┤
│ □ 团队人数 > 10 且多团队协作?        │
│ □ 不同模块有不同扩展需求?            │
│ □ 部署耦合导致发布缓慢?              │
│ □ 存在独立技术栈/合规需求?           │
│                                      │
│ 少于 2 项 → 保持模块化单体            │
│ 2-3 项    → 考虑拆分,但谨慎评估       │
│ 4 项以上  → 拆分 ROI 明确             │
└──────────────────────────────────────┘
         ↓ (确定拆分)
┌──────────────────────────────────────┐
│ 确定拆分边界                          │
├──────────────────────────────────────┤
│ 1. 画出业务能力图谱                    │
│ 2. 识别限界上下文                      │
│ 3. 对齐团队边界(康威定律)            │
│ 4. 确定数据所有权                      │
│ 5. 画出服务间依赖图                    │
│                                      │
│ 依赖图中有循环依赖?→ 重新审视边界     │
└──────────────────────────────────────┘
         ↓
┌──────────────────────────────────────┐
│ 确定拆分顺序与策略                    │
├──────────────────────────────────────┤
│ 1. 先拆外围,后拆核心                  │
│ 2. 先准备基础设施,再拆服务            │
│ 3. 每次只拆一个服务,验证后再拆下一个  │
│ 4. 数据库先用 Level 2 (独立 Schema)   │
│ 5. 设置回滚方案                       │
└──────────────────────────────────────┘

7.2 拆分检查清单

技术检查清单

组织检查清单

如果以上超过 3 项未满足,建议先把基础设施建设好再拆。


总结

核心原则回顾

  1. 业务边界优先:按业务能力拆分,不是按技术层次
  2. 数据边界优先:每个服务有自己的数据库
  3. 康威定律:服务边界应匹配团队边界
  4. 渐进式拆分:先拆外围、先建基础设施、逐步迁移
  5. 够用就好:3-8 个微服务通常比 30 个更好

一句话总结

好的微服务架构不是拆出来的,是长出来的。 先有模块化单体,当边界清晰后再自然裂变为微服务。不要为了微服务而微服务——业务价值才是最终目标。

行动指南

如果你正在...建议
从零开始新项目先用模块化单体,等业务边界稳定后再拆
单体已经很大识别限界上下文,先拆外围非核心服务
已经拆了很多服务检查是否有应该合并的"颗粒过小"服务
不确定要不要拆回答 7.1 节的 4 个问题,客观评估

参考资料

  1. Sam Newman, 《微服务设计》
  2. Chris Richardson, 《微服务架构设计模式》
  3. Martin Fowler, "Microservices - a definition"
  4. Melvin Conway, "How Do Committees Invent?"
  5. Alibaba, 《阿里微服务拆分实践》
上次编辑于:
贡献者: zhengtianqi