大项目架构设计:从需求到代码结构
掌握大型 Python 项目的架构设计方法论,能独立设计可维护的系统
- 理解分层架构和依赖方向
- 掌握项目目录结构设计
- 学会配置管理和环境隔离
- 理解依赖注入的价值
为什么需要架构设计?
小脚本一个文件就能搞定。但代码量超5000行、团队超3人时,没架构就是灾难:改一处崩三处、没人敢重构、新人上手要两周。架构设计就是管复杂度——把系统拆成高内聚、低耦合的模块,每个部分能独立理解、测试、修改。
架构不是画 UML 图,而是做「取舍」:单体还是微服务?同步还是异步?SQL 还是 NoSQL?每个决策都有代价。好的架构师能清晰说出每个选择的 trade-off,而不是追逐时髦技术。能用最简单的方案解决问题,就是最好的架构。
分层架构
最经典也最实用的架构是分层架构。核心原则:依赖只能从外层指向内层,内层不知道外层的存在。
# 典型的分层结构
# ┌─────────────────────────────┐
# │ API/接口层 (routes/) │ 接收HTTP请求,参数校验
# ├─────────────────────────────┤
# │ 业务逻辑层 (services/) │ 核心业务规则
# ├─────────────────────────────┤
# │ 数据访问层 (repositories/) │ 数据库操作
# ├─────────────────────────────┤
# │ 数据模型层 (models/) │ 数据结构定义
# └─────────────────────────────┘
#
# 依赖方向:API → Service → Repository → Model
# 内层不依赖外层:Service 不知道也不关心谁调用它(API或CLI)
# 反例:业务逻辑直接写在路由里(FastAPI/Flask新手常见)
@app.post("/orders")
def create_order(user_id: int, items: list):
# 校验、计算、扣库存、写数据库、发通知全堆在一起
# 问题:无法复用、无法测试、改一处影响全部
...
# 正例:分层
# routes/order.py
@router.post("/orders")
def create_order(req: OrderRequest):
return order_service.create_order(req.user_id, req.items)
# services/order_service.py
def create_order(user_id, items):
validate_items(items)
total = pricing_service.calculate(items)
order = order_repo.create(user_id, items, total)
notification_service.send_confirmation(order)
return order
# repositories/order_repo.py
def create(user_id, items, total):
return db.execute(
"INSERT INTO orders ...",
{"user_id": user_id, "total": total}
)高层模块不应该依赖低层模块,两者都应该依赖抽象。Service 不应该直接依赖具体的 Repository 类,而应该依赖接口(Protocol)。这样你可以在测试时用内存 Repository 替换数据库 Repository。
项目目录结构设计
# 一个中型 FastAPI 项目的推荐结构
myapp/
├── pyproject.toml # 项目配置和依赖
├── README.md
├── .env.example # 环境变量模板
├── src/
│ └── myapp/
│ ├── __init__.py
│ ├── main.py # 应用入口
│ ├── config.py # 配置(从环境变量读取)
│ ├── api/ # 接口层
│ │ ├── routes/
│ │ └── deps.py # 依赖注入
│ ├── core/ # 核心业务逻辑
│ │ ├── services/
│ │ └── exceptions.py
│ ├── db/ # 数据库
│ │ ├── models/
│ │ ├── repositories/
│ │ └── session.py
│ ├── schemas/ # Pydantic 模型(请求/响应)
│ └── utils/ # 工具函数
├── tests/
│ ├── unit/
│ ├── integration/
│ └── conftest.py
└── scripts/ # 运维脚本
# 关键原则:
# 1. src/ 布局:防止import混乱,打包更规范
# 2. 按功能分层,不按技术分层(api/core/db 而不是 models/views/controllers)
# 3. 每个模块有明确职责,一个文件不超过300行配置管理
# ❌ 硬编码配置(新手常见)
def connect_db():
return psycopg2.connect(
host="192.168.1.100", # 生产IP写死在代码里!
password="mypassword123", # 密码提交到Git!
dbname="myapp"
)
# ✅ 配置从环境变量读取
import os
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
db_host: str = os.getenv("DB_HOST", "localhost")
db_port: int = int(os.getenv("DB_PORT", "5432"))
db_name: str = os.getenv("DB_NAME", "myapp")
db_user: str = os.getenv("DB_USER", "postgres")
db_password: str = os.getenv("DB_PASSWORD", "")
debug: bool = os.getenv("DEBUG", "false").lower() == "true"
config = Config()
# 更好:用 pydantic-settings 自动校验
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
db_host: str = "localhost"
db_port: int = 5432
db_password: str
secret_key: str
debug: bool = False
model_config = {"env_file": ".env"}
settings = Settings() # 自动从环境变量和.env文件读取,缺失必填项直接报错别把密码、API Key、Secret 提交到 Git。用 .env 文件,把它加到 .gitignore 里,再配个 .env.example 模板。生产环境用环境变量或者密钥管理服务,比如 Vault、AWS Secrets Manager。Git 历史里的密码就算删了也能找回来,一旦泄露必须马上轮换。
依赖注入
# 不使用依赖注入:Service 内部直接创建 Repository(硬编码依赖)
class OrderService:
def __init__(self):
self.repo = PostgresOrderRepository() # 写死了,测试时无法替换
# 使用依赖注入:依赖从外部传入
class OrderService:
def __init__(self, order_repo, user_repo, notifier):
self.order_repo = order_repo # 任何实现了相同接口的对象都行
self.user_repo = user_repo
self.notifier = notifier
# 生产环境
service = OrderService(
PostgresOrderRepository(),
PostgresUserRepository(),
EmailNotifier()
)
# 测试环境:用内存实现和Mock替换
service = OrderService(
InMemoryOrderRepository(),
InMemoryUserRepository(),
MockNotifier()
)
# FastAPI 的 Depends 就是依赖注入容器
@router.post("/orders")
def create_order(
req: OrderRequest,
service: OrderService = Depends(get_order_service) # 框架自动注入
):
return service.create_order(req.user_id, req.items)小型项目(<3000行)不需要过度设计,直接实例化就行。当你需要:写单元测试(替换外部依赖)支持多种实现(开发/测试/生产)解耦模块时,依赖注入就有用。
MVP 指的是?
技术选型最应考虑?
依赖倒置(DIP)的核心主张是?
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
大模型接口慢且偶发抖动,必须设连接与读取超时;重试只对网络错误、429、5xx 做指数退避并设上限,对 400 类参数错误重试毫无意义。写操作(如生成并落库)要带幂等键,避免超时重试产生重复记录。还要对总耗时设预算:主模型超时就降级到小模型或本地兜底,绝不能让一次外部调用拖垮整个界面。
挑战任务
设计项目结构
给博客系统搭分层架构和目录结构,各层职责和依赖关系这么定:
课后作业
重构脚本为分层架构
找一个你写的超过200行的单文件脚本,用分层架构重构:拆出 models/services/repositories,配置移到环境变量。