1. 为什么中小团队需要Python架构规范
在创业公司待过的开发者都深有体会——当团队从3个人扩张到15人时,代码库往往会经历一场"混沌期"。我曾在某个电商项目里见过这样的场景:新来的工程师把业务逻辑直接写在Django的视图层里,两个月后当我们需要修改促销规则时,不得不像考古学家一样在2000行的views.py文件里寻找散落的业务逻辑。
这就是架构规范的价值所在。对于50人以上的大厂团队,他们有完善的架构评审和代码规范;但对于10人左右的中小团队,特别是快速迭代的创业公司,一套轻量但明确的Python架构规范能避免以下典型问题:
- 可维护性陷阱:随着业务复杂化,没有分层设计的代码会变成"面条式"结构,某个API的改动可能引发意想不到的连锁反应
- 协作效率低下:团队成员对代码应该放在哪个目录争论不休,PR(Pull Request)里充满关于代码位置的讨论而非逻辑优化
- 技术债累积:为了赶进度而妥协的架构决策,在半年后可能让新功能开发时间翻倍
一个好的Python架构规范应该像城市的交通标志——不限制你开什么车(具体实现),但告诉你该靠右行驶(分层原则)和哪里可以转弯(接口约定)。下面是我们团队经过三个项目迭代总结出的规范框架。
2. 基础分层架构设计
2.1 经典三层架构的Python实现
大多数Web项目适合采用分层架构(Layered Architecture),但传统的表现层-业务层-数据访问层(Presentation-Business-Data)划分在Python实践中需要调整:
python复制project_root/
│
├── app/ # 核心应用代码
│ ├── entrypoints/ # 表现层(原Presentation层)
│ │ ├── web/ # Web接口(FastAPI/Django Views)
│ │ ├── cli/ # 命令行接口
│ │ └── events/ # 消息处理器
│ │
│ ├── services/ # 业务服务层(原Business层)
│ │ ├── order_service.py
│ │ └── payment_service.py
│ │
│ ├── domain/ # 领域模型层
│ │ ├── entities/ # 纯业务对象
│ │ └── value_objects/ # 值对象
│ │
│ └── infrastructure/ # 基础设施层(原Data层扩展)
│ ├── repositories/ # 仓储实现
│ ├── external/ # 第三方服务调用
│ └── cache/ # 缓存组件
│
├── config/ # 配置管理
├── tests/ # 测试代码(保持相同结构)
└── scripts/ # 运维脚本
关键改进点:
- 将传统的"业务逻辑层"拆分为领域模型和业务服务两层,符合DDD(领域驱动设计)思想
- 基础设施层不仅包含数据库访问,还整合缓存、消息队列等第三方服务
- entrypoints替代传统的"表现层",明确区分不同入口协议(HTTP/CLI/Event)
提示:对于中小团队,建议先用这个基础结构,等单个服务超过5000行代码再考虑更复杂的六边形架构或CQRS模式。
2.2 层间交互规则
分层架构最怕变成"炒鸡蛋"——各层代码混作一团。我们制定了这些铁律:
-
单向依赖原则:
- entrypoints → services → domain ← infrastructure
- domain层应该是"纯净"的,不导入任何其他层的代码
-
数据传递规范:
- 层间传递的应该是领域实体或DTO(数据传输对象),而不是ORM模型或原始字典
- 示例:在Django项目中,应该在entrypoints中完成
Model→Entity的转换
python复制# 反模式:直接在视图层使用Django Model
def order_view(request):
orders = Order.objects.filter(user=request.user) # 暴露ORM细节
return render(orders)
# 正确做法
def order_view(request):
orders = OrderService.get_user_orders(request.user.id) # 返回领域实体
return OrderListDTO.from_entities(orders).dict()
- 异常处理边界:
- infrastructure层抛出的原始异常(如SQL错误)不应该穿透到entrypoints
- 在service层进行异常转换和统一处理
3. 业务逻辑组织模式
3.1 事务脚本 vs 领域模型
对于中小团队,我推荐采用混合模式——简单业务用事务脚本(Transaction Script),复杂业务用领域模型:
python复制# 事务脚本示例(适合简单逻辑)
class CouponService:
@staticmethod
def apply_coupon(order_id: int, code: str):
coupon = CouponRepository.find_by_code(code)
order = OrderRepository.get(order_id)
if coupon.is_expired():
raise CouponExpiredError()
order.apply_discount(coupon.amount)
OrderRepository.save(order)
coupon.mark_as_used()
CouponRepository.save(coupon)
# 领域模型示例(适合复杂逻辑)
class Order:
def apply_discount(self, amount: Decimal):
if self.status != OrderStatus.PENDING:
raise InvalidOrderStateError()
if amount > self.total_amount * 0.3: # 折扣不超过30%
raise DiscountExceedLimitError()
self.discount_amount = amount
self._trigger_domain_event(OrderDiscountAppliedEvent())
选择依据:
- 事务脚本:流程简单、分支少的业务(如CRUD操作)
- 领域模型:有复杂业务规则、状态变化的场景(如订单生命周期)
3.2 领域服务设计要点
-
服务边界划分:
- 按业务能力划分(如
PaymentService、InventoryService) - 避免出现
CommonService或UtilsService这种万能类
- 按业务能力划分(如
-
方法设计原则:
- 方法应该是无状态的(stateless)
- 参数和返回值尽量使用基本类型或领域对象
- 方法名应该反映业务意图(如
place_order而不是save_order)
python复制# 好的服务方法设计
class OrderService:
def place_order(self, user_id: int, items: List[OrderItem]) -> Order:
"""创建订单并预留库存"""
# 实现细节...
# 不好的设计
class OrderService:
def process(self, data: dict) -> dict:
"""模糊的职责和类型"""
4. 基础设施层实践技巧
4.1 仓储模式(Repository)实现
仓储模式是连接领域模型和数据库的桥梁,Python中的实现要点:
python复制# 基础接口定义
class OrderRepository(ABC):
@abstractmethod
def get(self, id: int) -> Order: ...
@abstractmethod
def save(self, order: Order): ...
# Django实现示例
class DjangoOrderRepository(OrderRepository):
def get(self, id: int) -> Order:
db_model = OrderModel.objects.get(pk=id)
return Order(
id=db_model.id,
items=[Item(i.product_id, i.quantity) for i in db_model.items.all()]
)
def save(self, order: Order):
# 实现转换和保存逻辑...
关键收益:
- 领域层不需要知道ORM细节
- 方便切换数据源(如从MySQL到MongoDB)
- 更容易进行单元测试
4.2 第三方服务调用规范
对于调用外部API(如支付网关、短信服务),建议:
-
定义抽象接口:
python复制class PaymentGateway(ABC): @abstractmethod def charge(self, amount: Decimal, card: CardInfo) -> str: ... -
实现适配器:
python复制class StripePaymentGateway(PaymentGateway): def __init__(self, api_key: str): self.client = stripe.Client(api_key) def charge(self, amount: Decimal, card: CardInfo) -> str: # 调用Stripe SDK并处理响应 return charge_id -
依赖注入:
python复制class PaymentService: def __init__(self, gateway: PaymentGateway): self.gateway = gateway
这样在测试时可以轻松注入Mock实现。
5. 测试策略与CI集成
5.1 分层测试金字塔
中小团队的测试资源有限,建议这样分配精力:
code复制 [少量]
UI/集成测试
/ \
[适量] / \ [大量]
服务层测试 单元测试
具体到Python项目:
-
单元测试(占比60%):
- 重点测试领域模型和工具函数
- 使用pytest + factory_boy生成测试数据
- 示例:
python复制def test_order_total_calculation(): items = [Item(price=100, quantity=2), Item(price=50, quantity=3)] order = Order(items=items) assert order.total_amount == 350
-
服务层测试(占比30%):
- 测试服务类的业务逻辑
- 使用unittest.mock替换外部依赖
- 示例:
python复制def test_coupon_application(): repo_mock = Mock(spec=OrderRepository) service = CouponService(repo_mock) # 测试优惠券过期场景 with pytest.raises(CouponExpiredError): service.apply_coupon(1, "EXPIRED_CODE")
-
集成测试(占比10%):
- 测试完整的API流程
- 使用TestClient调用FastAPI/Django应用
- 示例:
python复制def test_order_creation_flow(test_client): response = test_client.post("/orders", json={"items": [...]}) assert response.status_code == 201 assert Order.objects.count() == 1
5.2 CI流水线配置
对于GitHub Actions的推荐配置:
yaml复制name: CI Pipeline
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[test]
- name: Run linting
run: |
black --check .
flake8 .
- name: Run unit tests
run: pytest tests/unit -v --cov=app --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
integration-test:
needs: test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
steps:
- uses: actions/checkout@v3
- run: pytest tests/integration
关键点:
- 先运行快速的单元测试
- 集成测试使用真实的数据库(如PostgreSQL容器)
- 代码覆盖率与Codecov集成
6. 演进式架构实践
中小团队的架构需要保持演进能力,我们的经验是:
-
定期进行架构评估:
- 每3个月召开一次"架构回顾会"
- 使用"热度图"分析代码库:哪些目录频繁修改?哪些文件变得过大?
-
渐进式拆分:
- 当单个服务超过5000行代码时,考虑按业务边界拆分子模块
- 示例:从
order_service.py拆分为order_creation.py+order_fulfillment.py
-
技术债管理:
- 在项目看板中创建"架构改进"任务
- 每个sprint预留20%时间处理技术债
一个实用的Python项目度量脚本:
python复制# scripts/code_stats.py
from pathlib import Path
import pygount
def analyze_project(root: Path):
for file in root.glob("**/*.py"):
analysis = pygount.source_analysis(file, "pygount")
print(f"{file}: {analysis.code_count}行代码, {analysis.documentation_count}行注释")
if __name__ == "__main__":
analyze_project(Path("app"))
运行它会输出每个文件的代码和注释行数,帮助识别需要重构的热点文件。
