从微服务切分到 FastAPI 实战:先把系统拆明白,再把接口写出来

微服务和 FastAPI 听起来像两个不同层面的东西:一个偏架构,一个偏开发框架。放到实际项目里,它们其实很容易连起来理解。

微服务切分回答的是:“一个复杂系统应该按什么边界拆?”FastAPI 回答的是:“拆出来的服务,怎样快速、清晰地对外提供接口?”这篇文章就沿着这条线讲:先说微服务切分是什么、有什么用,再用 FastAPI 写一个小型订单服务,把概念落到代码里。

1. 微服务切分到底是什么

很多系统一开始都是单体应用。一个项目、一个数据库、一个部署包,用户、商品、订单、支付、物流都在里面。业务刚起步时,这种方式很好:代码集中,部署简单,排查问题也直接。

但系统变大以后,单体应用会慢慢变重。比如:

  • 订单模块改了一个字段,整个系统都要重新发布。
  • 支付接口出问题,可能影响订单、商品、用户等其他功能。
  • 大促时只有订单和支付压力大,但扩容时只能把整个应用一起扩。
  • 多个团队在同一个工程里开发,代码冲突和沟通成本越来越高。

微服务切分要解决的,就是这个阶段的问题。它不是简单地把代码拆成多个目录,也不是把 Controller、Service、DAO 分别拆成服务,而是按照业务边界,把一个大系统拆成多个可以独立开发、独立部署、独立演进的小服务。

以电商系统为例,可以先拆成这些服务:

服务 主要职责
用户服务 注册、登录、用户信息维护
商品服务 商品上架、下架、库存管理
订单服务 创建订单、查询订单、取消订单
支付服务 支付、退款、对账
物流服务 发货、物流轨迹、签收状态

这里的关键词是“业务边界”。订单服务关心订单生命周期,支付服务关心资金流转,物流服务关心履约过程。边界清楚了,服务之间才不会互相拉扯。

2. 微服务切分有什么用

微服务不是为了让架构图更好看。它真正的价值,是让复杂系统在长期演进时不至于越来越难改。

2.1 服务可以独立迭代

假设订单服务要支持“预约配送时间”,理想情况下,只需要修改订单服务自己的代码和接口。用户服务、商品服务、支付服务不应该因为这个需求被迫一起发布。

这对团队协作很有帮助。一个团队负责订单,一个团队负责支付,各自对自己的服务负责,边界清楚,协作会顺很多。

2.2 故障更容易被限制在局部

如果物流服务临时不可用,用户应该仍然可以浏览商品、下单、支付,只是暂时查不到物流轨迹。一个服务出问题,不应该把整个系统一起拖垮。

当然,微服务本身不会自动带来高可用。要真正做到故障隔离,还需要配合超时、重试、熔断、降级、限流、链路追踪等治理能力。

2.3 可以按压力单独扩容

大促期间,订单、支付、商品搜索通常是高压模块,用户资料修改、后台统计可能没那么忙。如果是微服务架构,我们可以只扩订单服务、支付服务和搜索服务,而不是把整个系统整体复制很多份。

这也是微服务常见的收益:扩容更精确,资源使用更经济。

2.4 技术选型更灵活

不同服务可以根据自身特点选择技术栈。比如:

  • 订单服务业务规则复杂,可以用 Java。
  • 支付服务对并发和稳定性要求高,可以用 Go。
  • 数据查询、AI 工具、内部管理接口,可以用 Python + FastAPI。

但这里也要克制。技术栈灵活不等于每个服务都换一种技术。团队能不能维护、能不能部署、能不能监控,往往比“这个框架是不是流行”更重要。

3. 微服务不是拆得越细越好

新手做微服务设计时,最容易犯的错就是过度拆分。

比如把用户服务继续拆成注册服务、登录服务、头像服务、地址服务、积分服务。看起来很“微”,但服务数量一多,接口调用、部署配置、日志追踪、数据一致性都会变复杂。最后可能业务还没变清楚,系统先变难维护了。

判断一个服务该不该拆,可以问几个很朴素的问题:

  • 这块业务有没有清晰的职责边界?
  • 它的数据是否有明确归属?
  • 它是否经常独立变化?
  • 它是否需要独立部署或独立扩容?
  • 拆出去以后,调用关系会不会变得更复杂?

微服务切分常用的原则,可以简化成下面这张表:

原则 含义
业务边界优先 先按业务领域拆,不要按技术层拆
高内聚低耦合 服务内部职责集中,服务之间依赖尽量少
数据自治 服务尽量拥有自己的数据,不直接共享数据库表
粒度适中 太粗会退回单体,太细会增加治理成本
可独立演进 一个服务升级,不应强迫其他服务同步修改

最需要避开的,是“分布式单体”:服务看起来拆开了,但数据库共享、接口强耦合、发布必须一起发。那样只是把单体应用的复杂度搬到了网络上。

4. 用电商系统看一次切分过程

假设我们要设计一个电商系统,第一版功能包括:

  • 用户注册、登录。
  • 查看商品列表和商品详情。
  • 加入购物车。
  • 创建订单。
  • 支付订单。
  • 查询物流。

第一步,不要急着建工程,先梳理业务能力:

用户能力:注册、登录、用户信息维护
商品能力:商品查询、库存展示、商品上下架
购物车能力:添加商品、修改数量、删除商品
订单能力:创建订单、查询订单、取消订单
支付能力:发起支付、支付回调、退款
物流能力:发货、轨迹查询、签收

第二步,看数据应该归谁管:

用户服务:users
商品服务:products, inventory
购物车服务:carts, cart_items
订单服务:orders, order_items
支付服务:payments, refunds
物流服务:shipments, shipment_events

第三步,再看服务之间怎么协作。比如创建订单时,流程可能是:

用户发起下单请求
  -> 订单服务校验请求参数
  -> 订单服务调用商品服务查询价格和库存
  -> 订单服务生成订单
  -> 订单服务调用支付服务创建支付单

这里有一个重要边界:订单服务可以通过接口调用商品服务,但不应该直接读取商品服务的数据库。商品价格、库存这些数据属于商品服务,订单服务需要时通过接口拿。这就是数据自治的基本要求。

5. FastAPI 是什么

服务边界想清楚以后,就会进入实现阶段:每个服务怎么对外提供能力?

如果团队使用 Python,FastAPI 是一个很适合开发 HTTP API 的框架。它的特点很明确:

  • 写法简洁,接口定义直观。
  • 基于 Python 类型注解做参数解析和校验。
  • 使用 Pydantic 定义请求体和响应数据结构。
  • 自动生成 Swagger UI 和 ReDoc 接口文档。
  • 支持依赖注入,适合封装鉴权、分页、数据库连接等通用逻辑。
  • 性能表现不错,适合内部服务、AI 工具服务、数据服务和轻量业务服务。

一个最小的 FastAPI 应用如下:

from fastapi import FastAPI

app = FastAPI(title="Order Service")


@app.get("/health")
def health_check():
    return {"status": "ok"}

安装依赖并启动服务:

pip install fastapi uvicorn
uvicorn main:app --reload

启动后访问:

http://127.0.0.1:8000/docs

就能看到自动生成的 Swagger 文档。这个能力在微服务开发里很实用,因为服务之间经常要对接口。文档自动跟着代码走,比单独维护一份接口文档省心很多。

6. 用 FastAPI 写一个订单服务

下面写一个简化版订单服务。它不连接真实数据库,用内存字典保存数据,重点看 FastAPI 如何处理路径参数、查询参数、请求体、Header 和依赖注入。

项目可以先这样放:

order_service/
  main.py

6.1 定义订单请求模型

创建订单时,请求体通常不是一个简单字符串,而是一段结构化 JSON,比如用户 ID、商品列表、收货地址。这种数据适合用 Pydantic 模型来描述。

from enum import Enum
from typing import List

from fastapi import Depends, FastAPI, Header, HTTPException, Query
from pydantic import BaseModel, Field


app = FastAPI(title="订单服务", version="1.0.0")


class OrderStatus(str, Enum):
    CREATED = "created"
    PAID = "paid"
    CANCELLED = "cancelled"


class OrderItem(BaseModel):
    product_id: int = Field(..., gt=0, description="商品 ID")
    quantity: int = Field(..., ge=1, le=99, description="购买数量")


class CreateOrderRequest(BaseModel):
    user_id: int = Field(..., gt=0, description="用户 ID")
    items: List[OrderItem] = Field(..., min_length=1, description="订单商品列表")
    address: str = Field(..., min_length=5, max_length=200, description="收货地址")

这里的 gt=0ge=1min_length=1 都是校验规则。比如调用方传了 quantity: 0,FastAPI 会直接返回 422 Unprocessable Entity,不需要我们在接口里手写一堆参数判断。

6.2 创建订单接口

接着写创建订单的接口:

orders_db = {}
next_order_id = 1


@app.post("/orders")
def create_order(payload: CreateOrderRequest):
    global next_order_id

    order_id = next_order_id
    next_order_id += 1

    order = {
        "order_id": order_id,
        "user_id": payload.user_id,
        "items": [item.model_dump() for item in payload.items],
        "address": payload.address,
        "status": OrderStatus.CREATED,
    }
    orders_db[order_id] = order

    return order

可以用 curl 测一下:

curl -X POST "http://127.0.0.1:8000/orders" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": 1001,
    "items": [
      {"product_id": 501, "quantity": 2},
      {"product_id": 608, "quantity": 1}
    ],
    "address": "北京市海淀区某某路 1 号"
  }'

返回结果类似:

{
  "order_id": 1,
  "user_id": 1001,
  "items": [
    {"product_id": 501, "quantity": 2},
    {"product_id": 608, "quantity": 1}
  ],
  "address": "北京市海淀区某某路 1 号",
  "status": "created"
}

这就是 FastAPI 里很典型的请求体用法:用 BaseModel 定义结构,在接口函数里直接接收模型对象。JSON 解析、类型转换、字段校验都由框架完成。

6.3 路径参数:查询单个订单

订单 ID 是资源标识,适合放在路径里:

@app.get("/orders/{order_id}")
def get_order(order_id: int):
    order = orders_db.get(order_id)
    if not order:
        raise HTTPException(status_code=404, detail="订单不存在")
    return order

请求示例:

GET /orders/1

如果调用方请求 /orders/abc,FastAPI 会发现 order_id 不是整数,并自动返回参数错误。这就是类型注解带来的好处:代码里写的是类型,运行时也能得到校验。

6.4 查询参数:分页和状态过滤

列表接口通常需要分页和筛选,这类参数适合放在 query string 里:

@app.get("/orders")
def list_orders(
    status: OrderStatus | None = Query(None, description="订单状态"),
    page: int = Query(1, ge=1),
    size: int = Query(10, ge=1, le=100),
):
    orders = list(orders_db.values())

    if status:
        orders = [order for order in orders if order["status"] == status]

    start = (page - 1) * size
    end = start + size

    return {
        "page": page,
        "size": size,
        "total": len(orders),
        "items": orders[start:end],
    }

请求示例:

GET /orders?status=created&page=1&size=20

Query 不只是给参数设置默认值,还可以加范围限制、描述信息。这些信息同样会出现在 Swagger 文档里。

6.5 Header 参数:做一个简单的内部鉴权

微服务之间调用接口时,通常会在 Header 里传内部 token、trace id、租户 ID 等信息。下面写一个非常简化的内部 token 校验:

def verify_internal_token(x_internal_token: str | None = Header(None)):
    if x_internal_token != "secret-token":
        raise HTTPException(status_code=401, detail="无效的内部服务 Token")
    return x_internal_token

然后把它作为依赖挂到取消订单接口上:

@app.post("/orders/{order_id}/cancel")
def cancel_order(
    order_id: int,
    _: str = Depends(verify_internal_token),
):
    order = orders_db.get(order_id)
    if not order:
        raise HTTPException(status_code=404, detail="订单不存在")

    if order["status"] == OrderStatus.PAID:
        raise HTTPException(status_code=400, detail="已支付订单不能直接取消")

    order["status"] = OrderStatus.CANCELLED
    return order

请求时带上 Header:

curl -X POST "http://127.0.0.1:8000/orders/1/cancel" \
  -H "X-Internal-Token: secret-token"

这里用到的是 FastAPI 的依赖注入。鉴权逻辑写一次,就可以复用到多个接口里。业务函数里只保留业务逻辑,代码会干净很多。

7. 把 FastAPI 放回微服务里看

上面的例子很小,但已经覆盖了微服务接口开发中几个关键点。

7.1 服务通过接口暴露能力

订单服务不应该把数据库表直接暴露给其他服务,而应该通过 HTTP API 提供能力:

POST /orders              创建订单
GET /orders/{order_id}    查询订单详情
GET /orders               查询订单列表
POST /orders/{id}/cancel  取消订单

其他服务按接口调用即可,不要绕过订单服务直接访问订单表。否则服务边界会被破坏,后续改表结构、改业务规则都会很麻烦。

7.2 请求模型就是服务契约

CreateOrderRequest 不只是一个 Python 类,它也是服务契约的一部分。它规定了调用方必须传哪些字段、字段类型是什么、取值范围是什么。

微服务越多,契约越重要。很多联调问题,本质上不是代码不会写,而是双方对接口字段、数据格式、错误返回的理解不一致。

7.3 自动文档减少沟通成本

FastAPI 的 /docs 会展示接口路径、参数、请求体结构和字段说明。前端、测试、其他后端服务都可以直接看文档调接口。

这不是锦上添花。对微服务来说,接口文档就是协作入口。自动生成的文档虽然不能替代完整的业务说明,但至少能保证基础接口信息和代码同步。

7.4 依赖注入适合封装通用逻辑

微服务里有很多逻辑不属于具体业务,但几乎每个接口都会用到:

  • 鉴权。
  • 获取当前用户。
  • 解析租户 ID。
  • 创建数据库会话。
  • 校验内部服务 token。
  • 生成或读取链路追踪 trace id。

这些逻辑都可以用 Depends 封装起来。这样业务接口不会被通用代码塞满,也更容易测试和复用。

8. 真实项目里还要补什么

上面的订单服务只是演示版。如果要往真实项目靠,还需要继续补几块。

8.1 拆分目录结构

接口多起来以后,不建议所有代码都放在 main.py。可以拆成类似这样的结构:

order_service/
  app/
    main.py
    routers/
      orders.py
    schemas/
      order.py
    services/
      order_service.py
    repositories/
      order_repository.py
    dependencies/
      auth.py

各目录职责可以这样理解:

目录 职责
routers 定义接口路由,处理 HTTP 入参和返回
schemas 定义 Pydantic 请求模型和响应模型
services 放业务逻辑
repositories 放数据库访问逻辑
dependencies 放鉴权、分页、数据库连接等通用依赖

8.2 增加健康检查

微服务部署到 Docker、Kubernetes 或其他平台时,通常需要健康检查接口:

@app.get("/health")
def health_check():
    return {"status": "ok", "service": "order-service"}

部署平台可以通过这个接口判断服务是否还活着,网关或注册中心也可以据此做流量调度。

8.3 调用其他服务时要处理失败

订单服务创建订单时,可能要调用商品服务校验库存:

import requests


def check_inventory(product_id: int, quantity: int):
    response = requests.get(
        f"http://product-service/products/{product_id}/inventory",
        timeout=3,
    )
    response.raise_for_status()

    inventory = response.json()["available"]
    if inventory < quantity:
        raise HTTPException(status_code=400, detail="商品库存不足")

这个例子能说明服务调用,但真实项目还要更严谨:

  • 必须设置超时时间,不能无限等待。
  • 失败要有明确处理策略,比如重试、降级或返回可理解的错误。
  • 关键链路要记录日志和 trace id,方便排查跨服务问题。
  • 跨服务写操作要认真设计数据一致性,不能靠“调用顺序刚好成功”来赌。

这也是微服务的现实面:它让服务边界更清楚,也把网络、治理和一致性问题带到了台前。

9. 什么时候适合用 FastAPI 做微服务

FastAPI 很适合这些场景:

  • 内部管理后台接口。
  • AI 应用的工具服务。
  • 数据查询服务。
  • 轻量业务微服务。
  • 原型验证和快速交付。
  • 对接口文档、参数校验要求比较高的服务。

但如果团队已经有成熟的 Java/Spring Cloud 体系,也不一定要为了“换个新框架”而引入 FastAPI。技术选型要看团队经验、部署体系、监控能力和业务特点。

我的理解是:如果服务偏轻量,团队熟悉 Python,业务里又有较多数据处理、AI 工具集成或内部接口开发,FastAPI 是一个很顺手的选择。它不会把简单问题复杂化,也能把接口契约表达得比较清楚。

10. 小结

微服务切分解决的是系统复杂之后的边界问题:哪些能力应该放在一起,哪些能力应该拆开,数据归谁管,服务之间怎么协作。

FastAPI 解决的是服务实现阶段的效率问题:怎样快速写出有参数校验、有接口文档、结构清晰的 HTTP API。

把两者连起来看,完整路径大概是:

先按业务边界拆服务
再为每个服务设计清晰接口
然后用 FastAPI 这类框架实现接口
最后通过部署、监控、日志和治理能力让服务稳定运行

微服务不是目的,FastAPI 也不是目的。真正重要的是:当业务持续变化时,系统还能保持清楚、可维护、能交付。架构和框架最终都应该服务于这一点。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐