从微服务切分到 FastAPI 实战
从微服务切分到 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=0、ge=1、min_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 也不是目的。真正重要的是:当业务持续变化时,系统还能保持清楚、可维护、能交付。架构和框架最终都应该服务于这一点。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)