Django 生产级工程的分层哲学
Django 入门很快,快到容易让人误以为把业务逻辑堆进 View 就是正常的。我见过不少上线几年的系统,View 函数五百行,Model 的 save() 里塞着发邮件,ORM 查询随手就 for product in products: product.category.name——每次加需求都像在地雷阵里穿行。
问题不是写法「不优雅」,而是代码没有层次,责任没有边界,改一处就抖三处。Django 本身提供了足够的工具解决这件事,只是需要你主动搭好结构。
三层分工:Model、Service、View 各司其职
Django 的典型误区是把 Model 当「数据容器 + 业务处理器」一起用。正确的分工应该是:
- Model:描述数据结构,提供字段约束,写
QuerySet方法封装常用过滤链 - Service Layer:业务逻辑唯一入口——创建订单、处理支付、触发通知都在这里
- View / ViewSet:薄薄一层,负责解析请求、调 Service、返回响应
# 错误:业务逻辑混进 View
def purchase(request, pk):
product = Product.objects.get(pk=pk)
if product.stock == 0:
return Response({'error': 'out of stock'}, status=400)
product.stock -= 1
product.save()
Order.objects.create(user=request.user, product=product)
send_confirmation_email(request.user)
return Response({'ok': True})
# 正确:View 只负责 HTTP 翻译,业务在 Service
@action(detail=True, methods=['post'])
def purchase(self, request, pk=None):
product = self.get_object()
result = ProductService.purchase(product, request.user)
return Response(result, status=status.HTTP_201_CREATED)Service 函数用 @transaction.atomic 包裹涉及多表写入的操作,确保要么全部成功要么全部回滚——这是 View 层做不到也不该负责的事情。
QuerySet 方法:把「查询意图」变成可读词汇
N+1 查询是 Django 项目最常见的性能陷阱,根源是查询意图散落在各处、没有统一管理。自定义 QuerySet 可以解决这两个问题:
class ProductQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def with_category(self):
return self.select_related('category')
def with_tags(self):
return self.prefetch_related('tags')
# 调用方清晰表达意图,N+1 问题在定义处消除
Product.objects.active().with_category().with_tags()select_related 解决外键(JOIN 一次取完),prefetch_related 解决多对多(两次查询 + Python 侧拼装)。把这两个调用封进 QuerySet 方法,调用方不需要知道底层怎么取,也不可能「忘了加」。
如果一段查询逻辑在两个以上地方出现,就该封进 QuerySet 方法。如果一段业务逻辑需要操作两张以上的表,就该挪进 Service Layer。
Settings 分离:不同环境的配置不该混在一起
把所有配置塞进单文件 settings.py,然后用 if DEBUG 东拼西凑——这是很多项目的起点,也是后来维护头痛的来源。
更稳健的做法是按环境分文件:
config/settings/
├── base.py # 所有环境共享的基础配置
├── development.py # 本地开发,开 DEBUG,装 debug_toolbar
├── production.py # 生产,强制 HTTPS,收紧 HSTS
└── test.py # 测试,换内存邮件后端
development.py 和 production.py 都从 base.py 继承,只覆盖差异部分。生产环境的安全配置(SECURE_SSL_REDIRECT、SESSION_COOKIE_SECURE、SECURE_HSTS_SECONDS)就不会被开发配置的 DEBUG=True 意外冲掉。
缓存:先明确「什么数据值得缓存」
Django 提供三个层次的缓存:整页缓存(cache_page)、模板片段缓存({% cache %})、低级缓存(cache.get/set)。
高频读、低频写、计算代价高的数据适合缓存。一个典型场景是带聚合的分类列表:
def get_popular_categories():
cache_key = 'popular_categories'
result = cache.get(cache_key)
if result is None:
result = list(
Category.objects.annotate(product_count=Count('products'))
.filter(product_count__gt=10)
.order_by('-product_count')[:20]
)
cache.set(cache_key, result, timeout=60 * 60)
return result注意 list() 强制执行 QuerySet——Django ORM 是惰性的,不加 list() 存进缓存的只是一个未执行的 QuerySet 对象。
一句话心法
在 Django 里,代码在哪一层写比怎么写更重要——层次清晰的项目,任何一处改动的影响范围都是可预测的。