详细摘要 摘要

生成: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,但其创建对象的逻辑被隐藏在框架深层的抽象(如 Serializercreate 方法)中。
    • 问题: 当同一业务逻辑需要在不同地方(如异步任务、管理命令、其他视图)复用时,会导致代码重复或产生不合理的调用(如为了复用逻辑而去实例化一个 API 类)。
  • 序列化器 (Serializers)

    • 核心职责:
      • 将 Python 对象或 ORM 对象转换为 JSON。
      • 将传入的 JSON 数据转换为 Python 数据结构。
    • 错误用法:
      • 重写 createupdate 方法来实现复杂的对象创建逻辑。演讲者强调:> “创建对象不是序列化器的工作,而是你的工作。” 这同样违反了关注点分离原则。

解决方案:引入服务层 (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 视图不再包含任何业务逻辑,它们只负责:
    1. 接收请求和验证输入(通常通过一个输入序列化器)。
    2. 调用相应的 Service 或 Selector。
    3. 使用输出序列化器格式化返回结果。
  • 这种模式使得所有 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 应用。这是一种从经验中提炼出的、行之有效的架构模式。