这一节总结下工程化的区别,使用 DDD 领域驱动开发。以用户、订单、结算三个模块为例。
Plaintext
my_fastapi_project/
├── app/
│ ├── __init__.py
│ │
│ ├── core/ # 【核心基础设施层】(类似于 Java 的 'common' 包)
│ │ ├── __init__.py
│ │ ├── config.py # 读取环境变量 (Pydantic Settings)
│ │ ├── database.py # 数据库连接池, get_db_session (Lifespan, Depends)
│ │ ├── security.py # JWT, 密码哈希
│ │ ├── kafka.py # Kafka Producer 单例
│ │ └── exceptions.py # 全局异常定义
│ │
│ ├── modules/ # 【业务领域层】(你的 Feature Modules)
│ │ ├── __init__.py
│ │ │
│ │ ├── users/ # [用户模块]
│ │ │ ├── __init__.py
│ │ │ ├── router.py # Controller (API 路由)
│ │ │ ├── schemas.py # DTO (Pydantic 模型)
│ │ │ ├── models.py # Entity (SQLAlchemy 模型)
│ │ │ └── service.py # Service (业务逻辑)
│ │ │
│ │ ├── orders/ # [订单模块]
│ │ │ ├── __init__.py
│ │ │ ├── router.py
│ │ │ ├── schemas.py
│ │ │ ├── models.py
│ │ │ └── service.py
│ │ │
│ │ └── settlements/ # [结算模块]
│ │ ├── __init__.py
│ │ ├── worker.py # Kafka 消费者逻辑 (Consumer)
│ │ ├── models.py
│ │ └── service.py # (这里可能没有 router,因为它不暴露 API)
│ │
│ └── main.py # 【HTTP 入口】 (FastAPI App, Lifespan, Include Routers)
│
├── tests/ # 测试代码 (Pytest)
│ ├── conftest.py # 测试夹具 (Fixtures)
│ └── ...
├── .env # 本地环境变量
├── Dockerfile
└── pyproject.toml # 依赖管理 (Poetry / Maven pom.xml)
路由驱动开发就是以业务分块,如上图基础的用户、订单、结算分别有三块,再加上抽象出来的 core,这是最基本的模块,业务代码在 modle 里面,再去调用自模块的内容,比如同时用到订单和资金的接口。
相比较传统把所有业务代码都放到 Controller、Service、Dao 里面更容易拆分,也保证文件夹下的文件不要太多。

Spring 可以自动处理循环引用,比如 ServiceA 和 ServiceB 互相依赖,只要没有方法的循环调用,通常是没有问题的,解决方案可以改为构造器注入。
在 Python 中会报 ImportError: cannot import name 'X' (Circular Import)。完全不能相互引用。解决方案就是上面说的,在 modle 里面新写个 Strategy 调用 ServiceA 和 ServiceB。
我认为这是个好事,尽管在 Spring 也要求 DDD,但不像 Python 这样如此强硬,还是会出现 Service 循环调用的问题。单向依赖从架构层面统一。
以下是示例文件代码
Python
# app/main.py
from fastapi import FastAPI
from contextlib import asynccontextmanager
# 导入核心配置
from app.core.database import init_db_pool, close_db_pool
# 导入业务路由
from app.modules.users.router import router as user_router
from app.modules.orders.router import router as order_router
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db_pool()
yield
await close_db_pool()
app = FastAPI(lifespan=lifespan)
# 注册路由,加上前缀
# 这样 /users/login 就会生效
app.include_router(user_router, prefix="/users", tags=["Users"])
app.include_router(order_router, prefix="/orders", tags=["Orders"])
@app.get("/health")
async def health_check():
return {"status": "ok"}
Python
# app/modules/users/router.py
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db # 从 Core 导入基础设施
from . import schemas, service # 从当前目录导入业务逻辑
# 注意:这里用 APIRouter,不是 FastAPI
router = APIRouter()
@router.post("/login", response_model=schemas.Token)
async def login(
form_data: schemas.LoginRequest,
db: AsyncSession = Depends(get_db)
):
user = await service.authenticate_user(db, form_data.username, form_data.password)
return user
用户、订单、结算:边界在哪里?
这三者在业务上必须解耦,因为它们的变化频率和关注点完全不同。
A. 用户域 (User Context) - "我是谁" 关注点:身份认证 (Auth)、个人信息、权限、会员等级。
边界:它不关心你买了什么,也不关心你有没有钱。它只管"这个 Token 是不是合法的张三"。
关键数据:username, password_hash, email, role。
B. 订单域 (Order Context) - "业务契约" 关注点:交易的意向和状态流转。下单、取消、发货、收货。
边界:它引用了用户(通过 user_id),但它不包含用户的详细信息。它引用了支付结果,但它不处理金钱的计算。
关键数据:order_no, user_id, product_list, status (PENDING/PAID/SHIPPED), total_amount (快照)。
C. 结算/资金域 (Settlement Context) - "真金白银" 关注点:记账、流水、对账。这是系统的底线,必须绝对精确,通常只能追加 (Append-only),不能修改。
边界:它不关心用户买了什么商品,它只关心"用户A 给了 平台B 100元"。
关键数据:transaction_id, account_id, amount (+/-), type (PAYMENT/REFUND), balance_snapshot。
如果涉及多模块事物,用数据库事物即可。
依赖管理
Poetry 对应 Spring 中的 Maven 和 Gradle。
Python 中最原始用的 pip + Requestments.txt,但这个文件是扁平的,不像 Maven 有依赖树状图,项目大了后分不清哪些是自己要用的,哪些是间接引入的。
Poetry 中 pyproject.toml 相当于 pom.xml,poetry.lock 相当于解析后树状图。
Maven 中所有的 jar 包在 ~/.m2/repository,Python 中用虚拟环境,一个项目一个环境,项目之间不受影响。这也由 Poetry 自动管理。
配置文件示例。
TOML
[tool.poetry]
name = "my-fastapi-project"
version = "0.1.0"
description = "重写 Java 订单系统的 Python 版本"
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.10" # 指定 Python 版本
fastapi = "^0.109.0"
uvicorn = {extras = ["standard"], version = "^0.27.0"}
sqlalchemy = {extras = ["asyncio"], version = "^2.0.0"}
aiokafka = "^0.10.0"
# 注意:这里只列出你直接使用的包,依赖的依赖不会出现在这里
[tool.poetry.group.test.dependencies]
pytest = "^8.0.0"
httpx = "^0.26.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Maven 和 Poetry 命令对比

本地用 poetry install,线上用 Poetry 转成 requirements.txt 用 pip 安装,这样 Docker 无需依赖 poetry 速度更快、镜像更小。
生产和部署
Gunicorn + Uvicorn + Docker 是一套标准的线上部署方案。
Docker 和 k8s Java 也在用,之前说过 Python 由于 GIL 是单线程模式,为了充分利用服务器的多核,需要使用 Gunicorn + Unicorn,有多少个核心就用 Unicorn 启动多少个 Python 服务,每 2 个服务绑定 1 个 CPU 核心,再加一个冗余的服务(官方推荐2 * 核心数 + 1),再由 Gunicorn 分发请求,类似 Nginx 的作用。
生产启动命令是: gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
参数详解:
main:app:
main:Python 文件名 (main.py)。
app:文件里面的 FastAPI 实例变量名 (app = FastAPI(...))。
--workers 4:
启动 4 个独立的 Python 进程。
计算公式:官方推荐 (2 x CPU核心数) + 1。如果你是 2 核 CPU,就配 5 个 Worker。
--worker-class uvicorn.workers.UvicornWorker:
关键! 告诉 Gunicorn:"别用你默认的同步 Worker,去用 Uvicorn 的异步 Worker"。
如果不加这个,asyncio 直接失效,性能极其低下。
--bind 0.0.0.0:8000:监听端口。
Dockerfile 示例
shell
# 1. 选择基础镜像 (Slim 版本体积小,不仅包含 Python,还包含必要的 C 库)
FROM python:3.10-slim
# 2. 设置环境变量
# 禁止 Python 生成 .pyc 文件
ENV PYTHONDONTWRITEBYTECODE=1
# 让 Python 日志直接输出到控制台 (Docker logs)
ENV PYTHONUNBUFFERED=1
# 3. 设置工作目录
WORKDIR /app
# 4. 安装依赖 (利用 Layer 缓存)
# 我们假设你已经用 poetry 导出了 requirements.txt
# (或者你可以用多阶段构建来做这一步)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 5. 复制业务代码
# 把 app 目录复制进去
COPY app /app/app
# 6. 创建非 root 用户 (安全最佳实践)
# Java 容器也推荐这么做,防止容器逃逸
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
# 7. 暴露端口
EXPOSE 8000
# 8. 启动命令 (使用 Gunicorn)
# 注意:在 Docker/K8s 里,通常 workers 也可以通过环境变量传入
CMD ["gunicorn", "app.main:app", "--workers", "4", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]