07-实用技巧和资源(Cookbook & DX & Resources)

Tomy
22 分钟阅读
61 次浏览
进阶指南:包含 Cookbook 实例、类型检查建议以及丰富的 Django 学习参考资料。
Django开发者体验最佳实践

Django Styleguide 中文翻译 - 实用技巧和资源

💡 本章导读

如果说前面的章节是在构建项目的“骨架”,那么本章则是赋予项目“灵魂”的实战百宝箱。这里汇集了我们在处理数十个大中型 Django 项目后,沉淀出的最具生产力的开发技巧。

这些技巧并非凭空构思,而是为了解决真实开发中的 确定性(Certainty)可维护性(Maintainability)开发效率(DX) 问题:

  1. 实战 Cookbook (通用更新服务)

    • 意义:解决传统 instance.save() 带来的全表字段刷新问题,实现“改哪里、写哪里”的精准控制。

    • 场景:在高并发或涉及复杂副作用(如修改姓名后自动触发邮件)的业务场景中,它是确保系统行为可预测的关键。

  2. 强类型化转型 (Mypy & 类型注解)

    • 意义:将 Bug 拦截在运行之前。在 Python 的动态性与团队协作的稳定性之间寻找平衡。

    • 场景:当项目规模超过万行,或需要频繁重构核心服务层时,类型注解是开发者唯一的“引路灯”。

  3. 业界实战案例

    • 意义:通过 Facturedo 等真实公司的背书,验证本套风格指南在商业环境下的抗压能力。

  4. 学习与替代资源

    • 意义:不盲目迷信单一标准,提供更广阔的视野,让你根据项目实际阶段灵活选型。

总之,这一章旨在帮助你从一个“会写 Django 的程序员”进化为“能掌控复杂工程的架构师”。


Cookbook(实用代码片段)

🎯 目的: 一些通用可复用代码的实现存储在这里。

通用更新服务

问题背景

在 Django 项目中,模型实例的更新(Update)操作几乎无处不在。然而,如果不使用统一的模式,简单的更新逻辑往往会演变成工程灾难。

1. 陷入“赋值泥潭” (Boilerplate Hell)

当你需要更新一个包含 10 几个字段的 Profile 时,代码往往长这样:

python
# ❌ 糟糕:机械、重复且容易手抖写错变量名
profile.bio = data.get('bio', profile.bio)
profile.location = data.get('location', profile.location)
profile.website = data.get('website', profile.website)
profile.phone = data.get('phone', profile.phone)
# ... 重复 10 次 ...
profile.save()
2. 极其隐蔽的 save() 遗漏

在复杂的业务逻辑分支中,很容易在某个 if 路径下修改了属性却忘了存库。

python
# ❌ 风险:逻辑复杂时极易漏掉保存操作
def update_user_status(user, data):
    if data.get('is_active'):
        user.is_active = True
        # 这里忘了写 user.save(),导致 Bug 极难排查
    ...
3. 盲目全量更新的性能与安全风险

默认的 instance.save() 会将所有字段重新写入数据库,这会带来:

  • 性能损耗:哪怕只改了一个字段,也要更新整行数据。

  • 竞态条件(Race Condition):如果两个进程同时操作同一个对象,全量更新会不小心覆盖掉另一个进程刚刚修改的其他字段。

4. 难以追踪“究竟改了没?”

在业务中,我们经常需要:“如果用户修改了邮箱,就发一封验证邮件”

python
# ❌ 痛苦:为了判断变化,你得手动记录旧值,代码极其啰嗦
old_email = user.email
user.email = data.get('email', user.email)
user.save()

if old_email != user.email:
    send_verification_email(user)

解决方案:model_update 服务

这个服务的核心逻辑其实非常直观,它扮演了两个关键角色:

  1. 自动化机械劳动:由于大多数基础字段(如姓名、简介)的更新逻辑实在太简单、太重复,我们不希望在 Service 中手动写几十个 if 分支。

  2. 业务触发的“侦测器”:这是它最大的价值。我们编写一个通用的函数来实时校测哪些字段更新了。一旦发现初始字段发生实质性改变,它会立刻亮起信号灯,让我们能够针对性地管理后续的副作用——比如更新数据库里的派生字段(如重新生成用户名)、发送验证邮件,或者推送异步任务

这种模式建立了一个 “业务哨兵” ,确保了源头数据变化与后续动作之间的一致性。

💡 模式的核心思想

model_update 的设计精髓可以概括为:“将对象的同步操作转化为声明式的数据映射,并对状态变更保持高度感知。”

它是如何化解前文痛点的?

  • 自动化 (Automation):用循环映射代替繁琐的手动 instance.field = value

  • 防御性 (Defensiveness):将 save() 锁死在函数内部,只要发生了变动就一定会存库。

  • 极致性能 (Performance):默认开启 update_fields,通过“脏检查(Dirty Check)”确保只写变动的列。

  • 语义化副作用 (Semantic Side Effects):将“字段变了没”这个判断从业务代码中抽离,通过一个布尔值 has_updated 赋予业务逻辑极其简洁的表达力。


🚀 进化之路:从“机械劳动”到“通用规范”

我们可以利用 getattrsetattr 这两个编程函数,来解决手动复制的问题,你可以把它们理解为getset的升级版。

  • instance.name = 'Tom':是硬编码,你必须提前知道字段叫 name

  • setattr(instance, 'name', 'Tom'):是变量化。这意味着你可以把 'name' 存在变量里,从而写出支持任意字段的代码。

这是实现“通用(Generic)”的必经之路。让我们看看演进过程:

Level 1:封装循环逻辑 (解决“手酸”问题)

不再手动一行行写赋值,而是通过一个字符串列表告诉函数:“帮我把这些字段对号入座”。

python
# 初级思路:批量自动赋值
def basic_update(instance, fields, data):
    for field in fields:
        if field in data:
            # 这里的魔法:把字符串 field 变成实实在在的属性操作
            setattr(instance, field, data[field])

    # ❌ 这里的缺点:即使一个字都没改,也会触发一次全表的数据库写入操作。
    instance.save()
Level 2:脏检查与精准保存 (解决“盲目写入”)

与其每个字段都改,不如先 “对比一下” 。如果新旧值一样,我们根本不需要动它。

python
# 进阶:引入脏检查 (Dirty Check)
def efficient_update(instance, fields, data):
    has_updated = False
    for field in fields:
        if field in data:
            new_value = data[field]
            current_value = getattr(instance, field) # 先“看一眼”旧值

            # 只有真的变了,才动手
            if current_value != new_value:
                setattr(instance, field, new_value)
                has_updated = True

    if has_updated:
        # ✅ 精准写入:只更新 fields 中指定的列,不干扰其他字段。
        # 这也解决了 Race Condition(多个进程同时改不同字段导致互相覆盖)的问题。
        instance.save(update_fields=fields)

    return has_updated

model_update 的底层实现

这是我们根据上述思路打造出的“通用武器”。建议将其放在项目的 common/services.py 中。

python
# common/services.py

from typing import Tuple, Iterable
from django.db import models


def model_update(
    *,
    instance: models.Model,
    fields: Iterable[str],
    data: dict
) -> Tuple[models.Model, bool]:
    """
    通用模型更新服务

    只更新指定的字段,并且只在值实际改变时才保存。

    Args:
        instance: 要更新的模型实例
        fields: 允许更新的字段列表
        data: 包含新值的字典

    Returns:
        (更新后的实例, 是否有字段被修改)
    """
    has_updated = False

    # 遍历允许更新的字段
    for field in fields:
        # 检查 data 中是否包含该字段
        if field not in data:
            continue

        # 获取新值
        new_value = data[field]

        # 获取当前值
        current_value = getattr(instance, field)

        # 只在值确实改变时才更新
        if current_value != new_value:
            has_updated = True
            setattr(instance, field, new_value)

    # 只在有更新时才保存
    if has_updated:
        # 使用 update_fields 优化 SQL 查询,确保原子性
        instance.save(update_fields=fields)

    return instance, has_updated

📝 实现详解

核心逻辑解析:

  1. 分治策略:只处理 fields 白名单中定义的字段,彻底杜绝了前端传多余字段导致的非法修改。

  2. 原子性保护:通过 instance.save(update_fields=fields),我们告诉 Django 只生成 UPDATE ... SET col1=val1, col2=val2 的 SQL,而不是刷新整行,这在多进程环境下是“救命”的。

  3. 零浪费原则:如果没有字段发生变化,has_updatedFalse,不仅省下了数据库写入开销,也防止了可能触发的 Model Signals(信号)。


Level 3:最终形态 - 业务感知的更新服务

这里的核心逻辑是:我们将底层繁琐的“数据清洗”和“数据库写入”逻辑(即 Level 1 和 Level 2)封装进了一个名为 model_update 的工具函数中。

而我们在业务服务层(如 user_update)中调用它。它们之间的联系如下:

  1. 交给专业的人做专业的事user_update 只需要声明哪些字段可以改,具体的赋值、对比、保存都交给内置了 Level 2 逻辑的 model_update

  2. 接收反馈model_update 执行完后会返回一个 bool 值。服务层根据这个反馈,决定是否触发后续的业务“副作用”(比如发送欢迎邮件)。

这就是关注点分离model_update 负责数据一致性user_update 负责业务流程

完整示例:user_update 服务
python
# users/services.py

from styleguide_example.common.services import model_update
from styleguide_example.users.models import User


def user_update(*, user: User, data) -> User:
    """
    用户更新服务

    Args:
        user: 要更新的用户实例
        data: 包含更新字段的字典

    Returns:
        更新后的用户实例
    """
    # 定义没有副作用的字段
    non_side_effect_fields = ['first_name', 'last_name']

    # 使用通用 model_update 服务更新这些字段
    user, has_updated = model_update(
        instance=user,
        fields=non_side_effect_fields,
        data=data
    )

    # 在这里更新有副作用的字段
    # (例如,username 是根据 first_name 和 last_name 生成的)
    if has_updated:
        user.username = generate_username(
            first_name=user.first_name,
            last_name=user.last_name
        )
        user.save(update_fields=['username'])

    # ... 执行一些与用户相关的额外任务 ...

    # 例如:发送通知
    if has_updated:
        from users.tasks import user_profile_updated_task
        transaction.on_commit(
            lambda: user_profile_updated_task.delay(user.id)
        )

    return user

📝 代码详解

1. non_side_effect_fields(白名单分拣):

python
non_side_effect_fields = ['first_name', 'last_name']

💡 这两个字段的目的是什么?

你可以直接把这种写法理解为 “数据分拣”“因果联动”

  1. 定义“信号源” 名单里的 first_namelast_name 本质上就是 业务信号发射器。因为在数据库中 username 是由它们决定的,所以把它们放在一起作为“诱因”。

  2. 状态变更触发器 只要名单里的字段变了(即使只改了一个字母),底层工具就会瞬间亮起 has_updated 信号灯。拿到信号后,你才去干重活(如重新计算 username 或更新订单总价)。这种 “因变 -> 果随” 的逻辑在所有数据库物理字段同步的场景中(如订单、档案、统计报表)都是通用的。

  3. 自动化降噪 把这些“体力活字段”收纳进白名单,是为了让 Service 主体只剩下 核心业务意图 。通过这一行代码,你就能一眼看清:“因为名字动了,所以我去同步更新了账户名。”

2. 调用 model_update:

python
user, has_updated = model_update(
    instance=user,
    fields=non_side_effect_fields,
    data=data
)

返回值:

  • user - 更新后的实例

  • has_updated - 布尔值,指示是否有字段被修改

3. 副作用字段的处理:

python
if has_updated:
    user.username = generate_username(...)
    user.save(update_fields=['username'])
  • 只在有更新时才处理副作用

  • 使用 update_fields 优化数据库查询



💡 这种模式的好处

好处说明
减少重复代码字段赋值逻辑集中在一处
性能优化使用 update_fields 只更新需要的字段
避免不必要的写入值未改变时不触发数据库更新
清晰的关注点分离无副作用字段 vs 有副作用字段
易于测试可以单独测试通用服务
追踪更新has_updated 让你知道是否有实际变化

使用场景示例

示例 1:简单更新

python
def profile_update(*, profile: Profile, data: dict) -> Profile:
    """更新用户个人资料"""
    simple_fields = ['bio', 'location', 'website']

    profile, has_updated = model_update(
        instance=profile,
        fields=simple_fields,
        data=data
    )

    return profile

示例 2:带验证的更新

python
def product_update(*, product: Product, data: dict) -> Product:
    """更新产品信息"""
    # 验证
    if 'price' in data and data['price'] < 0:
        raise ValidationError("Price cannot be negative")

    # 更新无副作用字段
    basic_fields = ['name', 'description', 'price', 'stock']
    product, has_updated = model_update(
        instance=product,
        fields=basic_fields,
        data=data
    )

    # 处理副作用
    if has_updated and 'price' in data:
        # 价格变化时,记录历史
        PriceHistory.objects.create(
            product=product,
            old_price=product.price,
            new_price=data['price']
        )

    return product

示例 3:带权限检查的更新

python
def order_update(*, order: Order, data: dict, user: User) -> Order:
    """更新订单(带权限检查)"""
    # 根据用户角色决定可更新的字段
    if user.is_staff:
        allowed_fields = ['status', 'notes', 'shipping_address']
    else:
        allowed_fields = ['notes']

    order, has_updated = model_update(
        instance=order,
        fields=allowed_fields,
        data=data
    )

    # 状态变化时发送通知
    if has_updated and 'status' in data:
        from orders.tasks import order_status_changed_task
        transaction.on_commit(
            lambda: order_status_changed_task.delay(order.id)
        )

    return order

完整实现参考

完整的实现可以在我们的示例项目中找到:

⚠️ 重要提示:

如果你要在项目中包含 model_update,请确保:


测试示例

python
# common/tests/services/test_model_update.py

from django.test import TestCase
from users.tests.factories import UserFactory
from common.services import model_update


class ModelUpdateTests(TestCase):
    def test_updates_only_changed_fields(self):
        """测试只更新改变的字段"""
        user = UserFactory(first_name='John', last_name='Doe')

        data = {
            'first_name': 'Jane',  # 会改变
            'last_name': 'Doe',     # 不会改变
        }

        user, has_updated = model_update(
            instance=user,
            fields=['first_name', 'last_name'],
            data=data
        )

        self.assertTrue(has_updated)
        self.assertEqual(user.first_name, 'Jane')
        self.assertEqual(user.last_name, 'Doe')

    def test_returns_false_when_no_changes(self):
        """测试没有变化时返回 False"""
        user = UserFactory(first_name='John')

        data = {'first_name': 'John'}  # 相同的值

        user, has_updated = model_update(
            instance=user,
            fields=['first_name'],
            data=data
        )

        self.assertFalse(has_updated)

    def test_ignores_fields_not_in_data(self):
        """测试忽略不在 data 中的字段"""
        user = UserFactory(first_name='John', last_name='Doe')

        data = {'first_name': 'Jane'}  # 只包含 first_name

        user, has_updated = model_update(
            instance=user,
            fields=['first_name', 'last_name'],  # 但允许两个字段
            data=data
        )

        self.assertTrue(has_updated)
        self.assertEqual(user.first_name, 'Jane')
        self.assertEqual(user.last_name, 'Doe')  # 保持不变

    def test_ignores_fields_not_in_allowed_list(self):
        """测试忽略不在允许列表中的字段"""
        user = UserFactory(first_name='John', is_staff=False)

        data = {
            'first_name': 'Jane',
            'is_staff': True,  # 不在允许列表中
        }

        user, has_updated = model_update(
            instance=user,
            fields=['first_name'],  # 只允许 first_name
            data=data
        )

        self.assertTrue(has_updated)
        self.assertEqual(user.first_name, 'Jane')
        self.assertFalse(user.is_staff)  # 未被更新

DX(开发者体验)

🎯 目标: 所谓 DX,就是让开发者在写代码时感到“丝滑”且“确定”。在 Django 开发中,这通常意味着更少的黑盒猜测,更多的 IDE 智能提示。


mypy / 类型注解:在灵活与严谨间寻找平衡

🧩 我们的核心理念

当涉及到 mypy 时,我们拒绝“一刀切”。我们的最高准则是:

类型注解不是为了取悦机器,而是为了在开发者之间建立清晰的“契约”,并赋予我们重构代码的底气。

🏗️ 实战策略:根据项目生命周期选型

在我们的工程实践中,我们会根据项目的规模、寿命团队规模,在以下两种策略间灵活切换。我们坚信:上下文(Context)是决定技术选型的唯一关键。

1. 🛡️ 严格模式 (Strict Mode) —— “大型战舰的稳压器”

如果你的项目预计会活过 1 年,且团队成员超过 3 人,请毫不犹豫地选择此模式。

  • 核心实践

    • 全量覆盖:所有 services.pyselectors.py 必须有 100% 的类型注解。

    • CI 强管控:将 mypy 校验挂载到 Git Hook 或 CI 流程中,报错即禁止合并。

    • 禁止 Any:在配置中开启 disallow_any_generics = True

  • 带来的 DX 价值

    • 重构信心:当你修改一个核心 Service 的参数类型时,Mypy 会瞬间告诉你全站还有哪 20 个地方需要跟着改。

    • 活文档:函数签名就是最好的说明书。不看代码实现,你就知道输入什么,产出什么。

2. ⚡ 宽松模式 (Loose Mode) —— “灵活快艇的加速器”

如果你的项目处于极其早期的 MVP(最小可行性开发)阶段,或者是一个几个周内就会结束的一次性项目。

  • 核心实践

    • ⚠️ 局部注解:只在最复杂的业务逻辑处写注解。

    • ⚠️ 关闭严格校验:可能完全不运行 mypy,只依靠 Python 运行时的动态特性。

  • 带来的 DX 价值

    • 极速迭代:省去了定义复杂 TypedDict 和解决 django-stubs 类型冲突的时间。

    • 降低门槛:让对类型系统不熟悉的开发者也能快速上手贡献代码。


💡 核心原则:上下文(Context)决定一切

不要因为追求“政治正确”而强行引入 Mypy。

  • 如果你在开快艇(小型项目),背着沉重的装甲(严格类型)会让你沉没。

  • 如果你在开航母(千人协作的大型 Django 应用),没有雷达和损管系统(强类型检查),任何一个小疏忽都能让系统瞬间瘫痪。


配置示例

Django-Styleguide-Example 中,我们配置了 mypy,使用:

你可以将其作为示例参考。

mypy.ini 配置示例
ini
[mypy]
python_version = 3.11
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
plugins =
    mypy_django_plugin.main,
    mypy_drf_plugin.main

[mypy.plugins.django-stubs]
django_settings_module = config.django.base

[mypy-*.migrations.*]
ignore_errors = True

[mypy-*.tests.*]
ignore_errors = True

[mypy-factory.*]
ignore_errors = True
类型注解示例

服务层:

python
from typing import Optional
from django.db import transaction
from users.models import User


@transaction.atomic
def user_create(
    *,
    email: str,
    first_name: str,
    last_name: str,
    password: Optional[str] = None
) -> User:
    """
    创建用户服务

    Args:
        email: 用户邮箱
        first_name: 名字
        last_name: 姓氏
        password: 密码(可选)

    Returns:
        创建的用户实例
    """
    user = User.objects.create_user(
        email=email,
        first_name=first_name,
        last_name=last_name,
    )

    if password is not None:
        user.set_password(password)
        user.save(update_fields=['password'])

    return user

选择器层:

python
from typing import Optional, Iterable
from django.db.models import QuerySet
from users.models import User


def user_list(
    *,
    filters: Optional[dict] = None
) -> QuerySet[User]:
    """
    用户列表选择器

    Args:
        filters: 过滤条件字典

    Returns:
        用户查询集
    """
    filters = filters or {}

    qs = User.objects.all()

    return qs.filter(**filters)


def user_get(*, user_id: int) -> User:
    """
    获取单个用户

    Args:
        user_id: 用户 ID

    Returns:
        用户实例

    Raises:
        User.DoesNotExist: 用户不存在时
    """
    return User.objects.get(id=user_id)

API 层:

python
from typing import Any
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import serializers, status


class UserCreateApi(APIView):
    class InputSerializer(serializers.Serializer):
        email = serializers.EmailField()
        first_name = serializers.CharField()
        last_name = serializers.CharField()
        password = serializers.CharField()

    def post(self, request: Request) -> Response:
        serializer = self.InputSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        from users.services import user_create

        user = user_create(**serializer.validated_data)

        return Response(status=status.HTTP_201_CREATED)

额外资源

另外,这个特定的项目也有 mypy 配置:


💡 决策指南

何时使用严格的类型检查?

推荐使用的场景:

  • 大型项目(超过 10,000 行代码)

  • 多人协作的项目

  • 长期维护的项目

  • 复杂的业务逻辑

  • 公共 API 或库

  • 重构遗留代码时

⚠️ 可以不用的场景:

  • 小型项目或原型

  • 快速迭代的早期阶段

  • 团队不熟悉类型注解

  • 短期项目


最佳实践

1. 渐进式采用:

python
# 从关键部分开始
# services.py - 严格类型检查
def user_create(...) -> User:
    ...

# 逐步扩展到其他部分
# models.py, apis.py 等

2. 忽略生成的代码:

ini
[mypy-*.migrations.*]
ignore_errors = True

3. 为第三方库添加存根:

bash
pip install django-stubs
pip install djangorestframework-stubs
pip install celery-stubs

4. 在 CI/CD 中运行:

yaml
# .github/workflows/ci.yml
- name: Run mypy
  run: mypy .

弄清楚什么对你最有效。


额外资源和替代方案

📚 学习更多: 我们发现有用的额外资源和其他替代方案,可以为风格指南增加价值。

推荐视频

1. Scaling Django to 500 apps

2. Django structure for scale and longevity

3. Quality Assurance in Django


替代方案和相关项目

1. Django API Domains

  • 🔗 django-api-domains

  • 另一种组织 Django 项目的方法

  • 强调 API 优先和领域驱动设计

特点:

  • 按领域(domain)组织代码

  • API 优先的方法

  • 清晰的架构边界

对比 Django Styleguide:

  • Django Styleguide 更注重服务层和选择器

  • Django API Domains 更强调领域边界

  • 两者可以互补使用


2. Django Design Patterns

  • 多种 Django 设计模式的集合

  • 适合学习不同的架构方法


3. Two Scoops of Django

  • 经典的 Django 最佳实践书籍

  • 涵盖广泛的主题


社区讨论

Hacker News 讨论:

主要讨论点:

  • 服务层 vs Fat Models

  • 何时使用哪种模式

  • 不同规模项目的选择

  • 团队采用的挑战


🧠 核心设计哲学与灵感来源

💡 致敬与溯源: 本风格指南并非凭空创造,而是站在巨人的肩膀上,融合了软件工程领域的经典思想与现代 Web 开发的最佳实践。

1. 关注点分离 (Separation of Concerns)

这是整个指南的基石。在传统的 Django 开发中(通常被称为 "Fat Models" 或 "View-Heavy" 模式),业务逻辑往往混杂在 Model 的 save 方法或 Controller(View)中。这种耦合导致了代码难以测试、难以复用。

本指南的解法: 我们将系统拆解为三个职责分明的层级,每一层只关注自己的“一亩三分地”:

  • Model 层:回归数据定义的本质,只负责“数据结构”和“数据库关系”。

  • Service 层:接管“业务逻辑”。它是系统的心脏,负责编排数据变更、触发副作用(如发邮件、调用第三方 API)。

  • API/View 层:负责“HTTP 交互”。它只关心如何解析请求、验证输入(Input Serializer)以及格式化响应(Output Serializer)。

2. 系统边界 (Inspired by "Boundaries" by Gary Bernhardt)

Gary Bernhardt 在其经典演讲《Boundaries》中提出了 “Shell(壳)与 Core(核)” 的概念:核心应由纯逻辑组成,而副作用应被推向外层的壳。

本指南的映射:

  • Core (逻辑内核):我们的 Selectors(选择器)Services(服务)

    • Selectors 往往设计为只读的、纯粹的查询逻辑。

    • Services 虽然包含副作用,但我们将“写数据库”和“发消息”等副作用显式地封装在其中,避免它们泄露到 View 或 Model 中。

  • Shell (交互外壳):我们的 APIsCelery Tasks

    • 它们作为系统的“边界”,负责与外界(用户、定时器)交互,然后迅速将控制权移交给内部的 Service。

通过建立清晰的边界,我们使得核心业务逻辑(Service/Selector)变得可测试(易于 Mock)且可移植(可以被 API 调用,也可以被命令行或 Celery 调用)。

3. 服务对象模式 (Rails Service Objects)

Ruby on Rails 社区为了解决 "Fat Models" 问题,广泛流行 Service Objects 模式。Django 社区虽然原生没有这个概念,但我们将其引入并本地化。

关键借镜:

  • 单一职责:一个 Service 函数通常只做一件事(如 user_create, order_ship)。这使得代码更加原子化。

  • 显式调用:我们摒弃了 Django Signals(信号)这种隐式的触发机制,转而在 Service 中显式调用 send_email()

    • 好处:当你阅读 user_create 代码时,你确切地知道发了邮件,而不是去猜“哪里藏了个 Signal 处理器”。

4. 认知负荷管理 (Cognitive Load Management)

我们认为:代码是写给人看的,顺便给机器运行。 复杂的架构虽然看起来“高级”,但如果增加了开发者的理解成本,那就是失败的设计。

如何降低认知负荷?

  • 一致性 (Consistency):无论你打开 users 应用还是 billing 应用,你都会看到完全相同的结构(services.py, selectors.py, apis.py)。这种可预测性让开发者能瞬间定位代码。

  • 显式优于隐式 (Explicit > Implicit)

    • 我们喜欢:user_update(user, data) -> 清晰的函数调用。

    • 我们不喜欢:重写 Model.save() 并隐藏大量副作用。

  • 扁平结构:我们推荐函数式编程(Functional approach)用于 Service 和 Selector,因为函数比类(Class)更简单,没有隐藏的状态(State),易于推理。


🌟 综合价值观

如果用四个词总结本指南的灵魂,那就是:

  1. 简单性 (Simplicity)

    • 拒绝过度设计。我们不引入复杂的 Repository 模式或并没有必要的 DDD 战术模式。我们只在 Django 原生基础上做最小限度的分层扩展。

  2. 一致性 (Consistency)

    • 给团队一个统一的“方言”。当所有人都用同一种方式写代码时,Code Review 变得容易,新成员入职也能迅速上手。

  3. 可维护性 (Maintainability)

    • 通过分层和解耦,我们确保了项目在扩展到 50+ 个 App、几十万行代码时,依然井井有条,不会坍缩成“大泥球”。

  4. 实用主义 (Pragmatism)

    • 这是一套**“经过实战检验”**的指南,而非象牙塔里的理论。所有的规则(如 model_update 的脏检查、Mypy 的宽松策略)都是为了解决真实工程痛点而生。


🔗 相关资源总览

官方文档

工具和库

学习资源

视频


🎉 结语

感谢你阅读完整个 Django Styleguide 中文翻译!

希望这份指南能帮助你:

  • ✅ 构建更好的 Django 项目架构

  • ✅ 编写更清晰、更可维护的代码

  • ✅ 提升团队的开发效率

  • ✅ 减少技术债务

记住核心原则:

  • 🎯 关注点分离

  • 🎯 保持简单

  • 🎯 一致性

  • 🎯 实用主义

继续学习:

  • 参考示例项目

  • 尝试在你的项目中应用

  • 根据你的需求调整

  • 与社区分享经验


祝你的 Django 项目开发顺利! 🚀