详细摘要 摘要
生成:2025-06-21 19:21摘要详情
- 音频文件
- 2018-08-31 | Radoslav Georgiev | Django structure for scale and longevity
- 摘要类型
- 详细摘要
- LLM 提供商
- openai
- LLM 模型
- gemini-2.5-pro
- 温度
- 0.3
- 已创建
- 2025-06-21 19:21:12
摘要内容
概览/核心摘要 (Executive Summary)
本次演讲由 Radoslav Georgiev 主讲,核心议题是如何构建可扩展、易于长期维护的 Django 项目。演讲者指出,随着项目功能增多和团队规模扩大,将业务逻辑随意放置在 Django 默认提供的“盒子”(如 Models, Views, Serializers)中,会导致代码混乱、难以维护和扩展。
演讲者提出的核心解决方案是引入一个明确的服务层 (Service Layer),作为业务逻辑的专属容器,从而将应用的核心逻辑与框架的接口(如 API、Views)分离开来。该架构模式主要包含两个新概念:
1. Services (服务):用于处理所有“写”操作(如创建、更新、删除对象)。它们是独立的、使用关键字参数和类型注解的函数,封装了所有与数据库写入相关的复杂逻辑、事务和副作用(如调用任务、发送邮件)。
2. Selectors (选择器):用于处理所有“读”操作(如列表查询、获取详情)。它们封装了复杂的查询、过滤和权限检查逻辑,解决了在 Model 属性中执行查询可能导致的 N+1 问题。
通过这种模式,API 和 Views 变得极其“薄”和标准化,仅作为调用 Services 和 Selectors 的入口,不包含任何业务逻辑。这种清晰的边界划分和可重复的模式,极大地提高了代码的可读性、可测试性和团队协作效率,是构建大型、长期 Django 项目的有效实践。
核心问题:业务逻辑应该放在哪里?
演讲者首先定义了业务逻辑:“所有与软件领域相关、非框架或工具类的代码,特别是定义约束和关系的 if 判断。” 随着项目复杂化,开发者面临的核心挑战是如何组织这部分占代码库约80%的业务逻辑。
对常见错误模式的批判
演讲者分析了将业务逻辑放置在传统 Django 组件中的弊端:
-
模型 (Models)
- 可接受的实践:
- 定义简单的、不产生额外数据库查询的属性(如
has_started)。 - 在
clean方法中实现额外的模型验证逻辑。
- 定义简单的、不产生额外数据库查询的属性(如
- 不推荐的实践:
- 在
save方法中堆砌大量业务逻辑(如创建关联对象、触发异步任务)。这会导致 “胖模型 (Fat Models)”,违反了单一职责原则,难以测试和扩展。“不要在你的
save方法中添加大量代码……模型应该只关心数据模型,而不是业务逻辑。”
- 在
- 可接受的实践:
-
视图与API (Views & APIs)
- 许多教程会将业务逻辑直接写在视图的
post方法中。 - 使用 Django Rest Framework (DRF) 的
ModelViewSet虽然能用几行代码快速生成 CRUD API,但其创建对象的逻辑被隐藏在框架深层的抽象(如Serializer的create方法)中。 - 问题: 当同一业务逻辑需要在不同地方(如异步任务、管理命令、其他视图)复用时,会导致代码重复或产生不合理的调用(如为了复用逻辑而去实例化一个 API 类)。
- 许多教程会将业务逻辑直接写在视图的
-
序列化器 (Serializers)
- 核心职责:
- 将 Python 对象或 ORM 对象转换为 JSON。
- 将传入的 JSON 数据转换为 Python 数据结构。
- 错误用法:
- 重写
create或update方法来实现复杂的对象创建逻辑。演讲者强调:> “创建对象不是序列化器的工作,而是你的工作。” 这同样违反了关注点分离原则。
- 重写
- 核心职责:
解决方案:引入服务层 (Service Layer)
为了解决上述问题,演讲者提倡引入新的“盒子”来专门存放业务逻辑,即服务层。
Services:处理“写”操作
- 定义: 在每个 app 中创建一个
services.py模块,用于存放所有执行数据库写入(创建、更新)的逻辑。 - 规范与特点:
- 函数签名: 使用 仅关键字参数 (keyword-only arguments) 和 类型注解 (type notations),这使得函数调用更明确,也为代码提供了自文档化的能力。
- 职责: 封装所有与创建或修改对象相关的重度逻辑,包括调用其他服务、触发 Celery 任务、处理事务等。
- 原则: > “每一个接触数据库的非平凡操作都应该在服务中完成。”
- 示例:
python # services.py def course_create(*, title: str, start_date: date, weeks_count: int) -> Course: # 1. 创建 Course 对象 course = Course.objects.create(title=title, start_date=start_date) # 2. 调用其他逻辑生成关联的 Week 对象 _generate_weeks_for_course(course=course, count=weeks_count) # 3. 调用异步任务 tasks.send_creation_notification.delay(course_id=course.id) return course
Selectors:处理“读”操作
- 定义: 对应地,在
selectors.py模块中存放所有从数据库读取数据的逻辑。 - 职责:
- 封装复杂的查询逻辑,特别是包含业务规则的过滤(如基于用户权限的过滤)。
- 处理权限检查和数据筛选。
- 解决的问题:
- 避免在 Model 属性中执行数据库查询,这种做法很容易在列表 API 中引发 N+1 查询问题。
- 经验法则: > “如果你的模型属性会产生数据库查询,并且不能简单地通过
select_related优化,那么它应该被移到一个 Selector 中。”
架构影响与最佳实践
采用服务层架构后,项目的其他部分也相应地发生了变化。
API 变得“薄”且可重复
- API 视图不再包含任何业务逻辑,它们只负责:
- 接收请求和验证输入(通常通过一个输入序列化器)。
- 调用相应的 Service 或 Selector。
- 使用输出序列化器格式化返回结果。
- 这种模式使得所有 API 的结构都高度一致(约5-7行代码),易于理解和维护,实现了 “可重复的模式”。
对序列化器的严格管理
- 防止复用: 为了避免因修改一个共享的序列化器而意外破坏多个 API,演讲者建议 将序列化器内联定义在 API 视图类内部。
- 输入与输出分离:
- 输出序列化器: 可以使用
ModelSerializer以节省代码。 - 输入序列化器: 严禁使用
ModelSerializer,应使用普通的serializers.Serializer。这可以防止ModelSerializer隐式地执行数据库创建操作,确保所有写操作都通过 Service 进行。
- 输出序列化器: 可以使用
- 工具: 演讲者展示了一个名为
inline_serializer的工具函数,它可以在不创建新类文件的情况下,动态地创建序列化器类,便于在视图中内联定义。
清晰的测试策略
- Models: 只需测试简单的属性和
clean方法中的验证逻辑。 - Services & Selectors: 这是测试的 核心和重点。测试需要与数据库交互,并大量使用
mock来隔离外部依赖(如其他服务、邮件发送等)。 - APIs: 由于 API 本身非常简单,通常 不需要进行单元测试,其功能由更高层级的 集成测试 覆盖。
结论与核心观点
演讲的核心思想是 通过明确的边界划分来管理复杂性。演讲者主张将 Django 应用划分为两个部分:
1. 核心 (The Core): 由 Services 和 Selectors 组成的业务逻辑层,它独立于框架,定义了应用的行为。
2. 外壳 (The Shell): 由 Django 的 Models, Views, APIs 等组件构成,负责与外部世界(如 HTTP 请求)交互,并将请求委托给核心业务逻辑层处理。
通过将业务逻辑严格限制在 Services 和 Selectors 中,可以构建出扩展性强、易于维护、团队协作顺畅的 Django 应用。这是一种从经验中提炼出的、行之有效的架构模式。