# 如何评价最近爆红的 FastAPI

用 fastapi 写过两个项目。

两个系统写下来后,对 fastapi 的使用有了比较粗浅的认识。也封装了一个极简脚手架,希望能帮助到有缘人。(文末有对这个脚手架的介绍)

https://link.zhihu.com/?target=https%3A//github.com/twocucao/tifa

首先抛出结论

fastapi 长远看好, 保持关注,但截至目前 (2021 年 4 月)写业务还是比较倾向于 django/flask。

gevent + flask/django 对比 async fastapi

当然, 本文的比较是不公平的, 因为我比较的是「现有服务迁移到 FastAPI」的一些痛点

综合了『上手成本』 『开发成本』 『迁移成本』后,我得出一个结论『迁移的边际效益不高』

分开说的话,主要有如下几个原因。

  1. 相对较弱的生态」 django/flask 相比生态太丰富了, 当我遇到一个问题, 当我想找一个插件, 都能比较轻松的找到。但 fastapi 尽管出来了好几年,但依然不如。async/await 的生态也是不如 sync 的生态。

  2. 其实 FastAPI 的一些卖点可以很方便的集成到你的项目中」比如 Pydantic + django/flask 甚至可以做的更加简单清晰。

  3. async await 调试成本较高」async await 对代码的侵入性很强,迁移简单的项目还行,成年老项目,迁移过程不见得很顺畅。对常规的 Web 性能的提升也没有到非要它不可的地步。gevent+django/flask 性能虽然没有那么强,但是依旧是耐打。

# 原因 1. 相对较弱的生态

生态粗略分为三种

  1. 围绕着框架本身的插件生态,比如 flask-sqlalchemy 很好的集成了 flask 框架本身和 sqlalchemy

  2. 一些开放平台的 sdk 生态,比如有的是官方出品 - aliyun-oss-sdk, 有的是社区贡献 - wechatpy 。

  3. io 的生态,由于上了异步,那么原先的请求库,数据库驱动等等 io 相关的库也要拿出来重新踩坑。

在挑选这些插件的时候,则会遇到这种或者那种的问题,比如

  1. django-debug-toolbar / flask-debug-toolbar 的 fastapi 替代品是啥?

  2. celery 的 async 替代品是啥,即使有我为何要抛弃一个成熟的 celery 去踩坑?听说, faust 挺火的, 但用了这个库, 代码组织结构都变太多了. 听说 aiotasks 也不错, 仔细一看, 代码最后更新时间是3 年前

  3. 为了新开启一个项目,我还得把支付宝 sdk 从同步代码改成异步代码?直接用他们的 sdk+gevent 不改一行代码不香嘛?数据库驱动,redis 库,请求库,支付宝 sdk,微信 sdk,各类开放平台 sdk,只要是涉及到 io 的库基本上都要换一套,但是现有的 codebase 线上已经踩过不少的坑了,为何要换?甚至要重写?

在经过很长时间的调研和一个一个插件的测试之后,我一拍脑袋,下了结论。

还是用成熟的东西省心呀,顺畅跑了那么多年的老代码,拷贝在身边,一用好多年。

django/flask + gevent 赛高!

# 原因 2. 其实 FastAPI 的一些卖点可以很方便的集成到你的项目中

fastapi 所说的亮点是什么性能好、编码速度快、减少人为的 BUG 符合直觉等等

但回过头来想想,除了 asyncio 和 自带 openapi/json schema, 其他的框架也有这些特点嘛比如写起来很快,简单、简洁

有人说,Pydantic 用来做校验器很好用。所以,我们要用 fastapi

可是,当我写 flask + pydantic 代码的时候,我写的代码是这样的

class LoginType(str, enum.Enum):
  MOBILE_CODE = "MOBILE_CODE"
  USER_PASSWORD = "USER_PASSWORD"

class VLogin(BaseModel):
  mobile_or_username: str # 这里可以加上更加复杂的校验
  password: Optional[str]
  type: LoginType

@bp.post("/login")
@validator
def do_login(data: VLogin):
  # read data and do login logic
  return user_dict

这个 validator 装饰器实现连换行加起来才 60 行。

所以说,在 flask/django 里,你完全不需要懂依赖注入。就可以把校验做的很干净。

啥,你还要文档? pydantic 可以直接导出 jsonschema ,写个简单的解析放到文档里就行了。

啥,你还要更加详细的文档丢给前端,那我觉得 graphql 可能是更加合适的选择。

你说 asyncio 并发量高, 但 gevent 并发量相比也不低呀

# 原因 3. async/await 调试成本比较高

为何说,调试成本较高

async await 对代码的污染性太强。

比如,风陵渡口初相遇, 一 await 误终身。 开发者深恶痛绝的「一次 await,处处 async」

底层模块一旦 await/async 了,则依赖于上面的所有模块函数都要 await, 如果不 await 代码就出问题。

设想一下,如果你用了别人写的函数,这个函数突然 async 了。万一 ci 跑过了发了版本,是不是感觉今天要先回滚后加班了?

当然,如果某一天有一种方法可以做到调用的时候,自动 await 的话,调试成本倒是低了不少。

# 那么这是否代表我不看好/不喜欢 FastAPI 呢?

恰恰相反,我很喜欢这个让人耳目一新的框架。

对于 FastAPI 的一些诟病,大多来自于我本人对 async/await 生态不了解(也可以理解为老了, 懒得踩坑了)。以及「我已经用好了这 flask/django, 没有必要再去为了踩坑新 Web 框架而踩坑」。

但这可以说成是「我懒」的问题, 也可以说是「新框架挑战老框架」的问题

毕竟某种角度上,苹果手机贵不一定是「苹果手机的贵」的问题,也可以说是「我穷」的问题。如果不能飞花摘叶, 那便老老实实玄铁重剑.

当我要做一个纯业务的系统,我的目标是快速上线验证市场。在这个阶段其实 gevent+flask/django 的性能是完全足够的。性能不足,扩容来凑。

在 gevent + flask/django 这种既有成熟解决方案的面前,FastAPI 的一些优点并没有解决我的痛点。

fastapi 实实在在解决了我的痛点的,是对 websocket 良好的支持。 如果我要写系统是聊天室系统,比如弹幕系统,比如实时协作。fastapi 应该是目前为止最好的解决方案了(相比 django channel/gevent)

总而言之 如果我的下一个系统没有历史负担, 需要的是并发数+Python, 而不是狂怼业务, 接各种 SDK, 那么 FastAPI 就是我的首选.

# 关于脚手架

我在踩坑 fastapi 的过程中封装了一套极简脚手架。

  1. FastAPI + Python 3.9 + poetry + Makefile
  2. tortoise-orm + tortoise-migration
  3. 自带 shell plus (如下图)

https://link.zhihu.com/?target=https%3A//github.com/twocucao/tifa