Service层不是可选项,而是Django项目超过3个核心模型或2名开发者后必须落地的基础设施,它通过解耦业务逻辑与数据模型、统一管理API调用/邮件发送/缓存等副作用、保障单元测试可维护性,避免models.py臃肿崩溃。

直接上结论:Service 层不是可选项,而是 Django 项目超过 3 个核心模型或 2 名开发者后必须落地的基础设施。它解决的不是“要不要写”,而是“不写就会在两周后被 models.py 里混着 API 调用、邮件发送、状态校验和缓存失效的 47 行 save() 重写逻辑搞崩溃”。
为什么不能把业务逻辑塞进 models.py
模型方法看似方便,但一旦出现以下任一情况,就已越界:
-
requests.post()、send_mail()、cache.set()出现在模型方法里——这会让单元测试无法干净 mock,迁移时可能意外触发副作用 -
get_full_name()内部调用外部 HTTP 接口——导致模板渲染{{ user.get_full_name }}变成网络请求瓶颈 - 同一个业务动作(如创建订单)在视图、管理命令、信号处理器里重复实现——字段校验漏一处,数据就错一片
-
Order.save()里顺手更新了库存、发了短信、清了 Redis 缓存——下次只想改库存逻辑,却得通读整个 save 流程
Service 类怎么写才不变成新坑
别用单例、别放类属性、别在 __init__ 里做耗时初始化。一个干净的 Service 应该是无状态的、参数驱动的、可预测的:
- 构造函数只接收明确依赖,比如
api_client、cache、logger,且全部设为None默认值,方便测试时传入 mock - 方法名动词开头、语义明确:
create_order()、refund_payment()、validate_inventory(),不叫do_stuff() - 输入输出用 Pydantic 模型(如
OrderCreateInput),不用dict或裸**kwargs——IDE 补全、类型检查、文档生成全靠它 - 绝不继承
models.Model,也不持有self.instance引用——Service 不是模型的附属品,它是独立的业务契约
怎么让 Service 真正接管业务入口
光写好 Service 类没用,必须切断旧路径。关键动作是「替换调用点」:
立即学习“Python免费学习笔记(深入)”;
- 所有视图中对
order.save()、user.send_welcome_email()的直接调用,全部替换成order_service.create_order(...) - DRF 的
Serializer.create()方法里删掉业务逻辑,只做数据转换,然后调order_service.create_order(serializer.validated_data) - Admin 的
save_model()中移除状态流转代码,改为order_service.transition_status(...) - 信号处理器(如
@receiver(post_save, sender=User))里只留纯副作用(日志、缓存),复杂判断和变更交给 Service 显式调用
CI 和团队落地的硬约束点
靠人自觉不持久,得用机器卡住红线:
- 在 CI 中加一行检查:
grep -r "requests\|httpx\|urllib\|send_mail\|celery\.task" models/,命中即失败 - 在每个
models.py顶部强制加注释模板:# ✅ OK: slug generation, updated_at update/# ❌ NO: API calls, cross-model updates, business rules - 用
pylint配置禁止models.*模块 importrequests或django.core.mail - Service 类统一放在
services/目录,命名带领域前缀:payment_service.py、inventory_service.py,不叫utils.py
最易被忽略的是:Service 不是“把模型方法剪下来贴过去”,而是重新定义边界——哪些算“数据操作”(留给模型),哪些算“业务决策”(交给 Service)。这个分界线画不准,Service 层很快会变成第二个臃肿的 models.py。


















