独立邮件订阅公司 Buttondown 最近发了篇博客,标题很直白——《What I love about Django》。写这篇文章的人是公司创始人兼工程负责人,按理说该是篇顺手的安利文。但他自己承认,一开始根本写不出来:盯着代码库,分不清哪些是"Django的功劳",哪些只是普通的 Python 写法和业务逻辑。
这个卡壳本身就是重点。一个框架用到让人忘了它存在,恰恰是它做对了的证明。但这也带来一个问题:文章里晒出来的几个"Django亮点",到底有多少是Django给的,又有多少是团队自己焊上去的?这个边界,原文没挑明,但值得挑明。
中间件:一个协议,干了五件事
Django 的中间件抽象足够简单:一个函数,接住 request,吐出 response,中间随便你插逻辑。Buttondown 用这一套协议做了子域名路由、UTM 归因、CSP 安全头、页面浏览记录、结构化日志绑定,还有一个给部署版本打戳的中间件——整个实现不超过十行代码。
协议越简单,可插拔的地方就越多。这是中间件设计的核心逻辑,也是Django原生就给的能力,没有争议。
模型层:哪些是Django给的,哪些是自己焊的
真正值得拆开看的是模型层。Buttondown 所有模型都继承自一个 BaseModel,这部分确实用到了 Django 的抽象基类(Meta.abstract = True)——这是框架原生特性。
但 BaseModel 里做的很多事,Django 文档里查不到:
- 定义一个
handle_<field>_change方法,字段一变就自动触发,不需要注册信号 - 把某个字段映射到一张 transition 表,每次变更自动落一行留痕记录
- 公开可见的带前缀 ID(比如
sub_...),由自定义 manager 悄悄解码 - 一个可选的软删除管理器
这些不是Django的能力,是Buttondown团队在Django的扩展点上自己搭出来的工程规范。
真正巧妙的地方在于:这些自建的约定,都是opt-in——加一个方法、加一行字典映射就能给模型接上新能力,不需要动基类,不需要写迁移脚本,不需要重构调用方。Django 提供的只是一个足够干净的扩展口子,剩下的活是团队自己干的。
Actions:把行为挪出模型类
Buttondown 不让模型类堆方法,而是把每个动作单独放一个文件,统一暴露 call()。"封禁一个订阅者"这种操作,写出来就是十行代码,还能组合调用别的 action(比如先终止付费订阅)。这也是团队自己定的规矩,Django 本身不管这层。
同样值得看一眼的,是文章专门列出的"不用清单":信号只留了一个(接 django-allauth),类视图完全不用,apps 模块化基本弃用,表单抽象直接跳过——前端走的是 Vue 水合的路子,Django 只负责吐一个 JSON 数据壳。
这套东西,能不能照抄
这篇文章标题叫"我爱Django",但读完你会发现,团队真正想夸的不是框架,是自己八年攒下的一套架构约定:中间件负责横切逻辑,模型基类负责能力叠加,Actions 负责行为归位。Django 提供的只是让这套约定长得住的地基,不是约定本身。
这也是为什么它未必能直接搬。团队规模、业务复杂度、要不要留痕审计,这些前提决定了 handle_<field>_change 这类钩子值不值得写。对一个小项目,这套东西可能是过度设计;对一个要长期跑变更审计的订阅系统,它是省了后面无数次事故排查的保险。
Buttondown 创始人自己也说得实在:2018 年选 Django,单纯因为他当时最熟这套,外加一条"少用创新代币"的老原则——宁可技术保守,也不想两头切换心智负担。八年过去,他后悔选了 Vue 做前端,但一次都没后悔选 Django。这句评价比任何功能列表都更有说服力:框架好不好,时间说了算,而不是发布会上的 PPT。
真正该记住的一句话,反倒藏在文章开头那句自嘲里——好框架用久了会隐形,分不清是它的功劳还是自己的功劳。这不是谦虚,是一个诚实的工程判断:选型这件事,选对的时候往往是安静的。
