XAION Tech Blog : FastAPI導入して良かった事について
はじめまして、XAION DATA CTOの石崎です。
XAION Tech Blog初投稿となります。
XAION DATAでは、機械学習を用いて収集・加工したウェブ上の公開データに対し、検索/マッチング機能を掛け合わせてWebサービスとして提供しています。1年目のサービス検証フェーズを終え2年目を迎えた今、オープンβ版を現在リリースしておりますが、多くの企業様にご利用いただくにあたり、システムの刷新を今回行いました。
本記事では、システムの刷新の際にFlask APIで実装していたAPIサーバーをFastAPIに移行したお話ができればと思います。
FastAPIって?
FastAPIは、Pythonの標準である型ヒントに基づいてPython 3.6以降でAPIを構築するための、モダンで、高速(高パフォーマンス)な、Webフレームワークです。
・高速: NodeJSやGo並みのとても高いパフォーマンス(StarletteとPydanticのおかげです)。
・高速なコーディング: 開発速度を約200%〜300%向上させます。
(公式HPより)
移行の背景
私がもともとデータサイエンス領域出身だったのと、XAION DATAは機械学習/データ加工技術を技術軸に事業を開始したということもあり、基本的にはPythonで開発されたソースが多数存在していました。特にAPIとして提供したいマッチングやデータ加工の処理は、ほとんどPythonで記述されていました。
既存のソースを活かしつつ、できるだけフロントエンドとバックエンドを分離して開発できる仕組みを実現するにあたり、軽量なPythonのAPIフレームワークであるFlaskを採用しました。
しかし、フロントエンドで取り扱うデータの増加に伴い、より明示的にデータの型を扱う必要が出てきたり、フロントで取得するデータの肥大化に伴いGraphQL等の新しい技術も取り入れていきたいといったニーズが出てきておりました。そのためFlaskから容易に移管ができ、かつ高速に動作するFastAPIを導入しました。
実際に利用してよかったところ
導入ハードルが低い
・2019年に日本で少し話題になったこともあり、日本での情報もちらほらある。またメンテが活発(2021年時点)なので安心感がある。
・日本語の公式ドキュメント有り。
・Flaskと類似した内容で記述できるためメンバーの学習コストが低い。
エコシステムとの相性について
XAION DATAのシステムは、複雑なデータクエリを生成することが多く、GraphQL等を導入するのに相性が良いです。
実際にFlaskをWebサーバー上で利用するときに、WSGI(Web Server Gateway Interface)をサポートするGunicornサーバーを利用します。一方FastAPIではASGI(Asynchronous Server Gateway Interface)に対応したUvicornサーバーを利用します。Asynchronnousという単語が入っている通り、標準で非同期処理が利用できますが、XAION DATAのシステムは非同期通信を利用したかったため、開発も大変容易になる、という魅力がありました。
ドキュメントの自動生成について
XAION DATAではSwaggerでFlask APIを開発していましたが、手動によるYAMLファイルのメンテナンスが定期的に必要で、ちょこちょこアップデート漏れが発生していました。
しかし、FastAPIではモデル定義を一度作ってそれなりに記載してあげれば、OpenAPIのYAMLファイルがワンクリックでExportできます。XAION DATAではAWSのAPI Gatewayを利用しており、OpenAPIのYAMLファイルを読み込むことでEndpointを簡単につくることができた、といった点も地味に助かりました。
Validationや命名規約について
pydanticをベースとしたData validation機能が優秀で、一度記載したValidationロジックを他のモデルで利用する際も使いまわしやすく、ロジックから分離して利用することが可能です。
また,pydanticのField TypesにはPydantic TypesやConstrained Typesなど便利な型が用意されており,例えば EmailStrなどを使えばEmailのバリデーションを宣言的にかけられます。
# FastAPIでサインアップ機能を作る
from pydantic import BaseModel, EmailStr, constr, conint
### EmailStrがある世界
class User(BaseModel):
email: EmailStr # emailアドレス以外の文字列はValidation Errorになる
password: str
@app.post('/signup')
async def signup(body: User):
# body.emailが正しいemailの形式になっているかのバリデーションを書く必要なし
......
### EmailStrがない世界
class User(BaseModel):
email: str
password: str
@app.post('/signup')
async def signup(body: User):
# emailのバリデーションをいちいち書かないといけない...
if '@' not in body.email:
raise HTTPException(status.HTTP_400_BAD_REQUEST)
......
### 他にも
class RequestBodyModel(BaseModel):
# nameがstr型かつ空文字ではない(文字列の長さが1以上)
name: constr(min_length=1)
# 0以上130以下(age=200などはValidation Errorになる)
age: conint(gt=0, lt=130)
命名規約等で得られた恩恵として、pydanticの外部モジュールでは、クエリパラメータやBody部のモデルでCamelCase等柔軟な設定が可能でかつ命名規約を明示できるため、命名規則が異なるPythonとTypeScriptのWebフロントを適切に管理することが可能です。
# snake caseをcamelモデルで返却
from fastapi_camelcase import CamelModel
class User(CamelModel):
first_name: str
last_name: str
@app.get("/user/get", response_model=User)
async def get_user():
return User(first_name="Taro", last_name="Yamada")
# 以下のようなデータを取得できます
# { firstName: "Taro", lastName: "Yamada" }
自動テストについて
DI(Dependancy Injection)が大変容易に利用できます。テストの時にDIするオブジェクトを自由に差し替えられるため、テストコードが簡潔に記載できたことでテストの生産性をぐっとあげることができました。他にもこれを利用してCleanArchitectureとかの実装もしやすくなるかなと思っています。(使いこなせるかはまた別のお話ですが・・)
例えば下記のコードではデータベースにアクセスするためのクラスをDIによって注入しています。
# 通常だとdbに渡されたuser_idの内容が返却される関数
@router.post('/di-test')
def di_test(access_log: AccessLog, db: Db = Depends(get_db)):
# 略(dbの処理)
return
get_dbがインスタンスを取得するための関数ですが、テスト時にはこのget_dbの動作を以下のように別の関数(例ではoverride_get_db)でオーバーライドする事が可能です。
app.dependency_overrides[get_db] = override_get_db
明示的にDependsと書くことによって、各エンドポイントが何に依存しているか(例えばデータベースを使用しているとか、リクエストヘッダに入っているJWTをデコードして利用している等)を明示できるのも良いと思います。もちろん、Mockを使うことで同等の事は行えますが、解りやすさという点で気に入っています。
まとめ
少し地味な内容のご紹介となってしまいましたが、チームで開発をしていくとどうしても、ソースの品質・規約・ドキュメントのメンテナンスは難しくなってしまいます。それらを守ってくれる仕組みが整っているのは大変ありがたいです。
今回の移行時の使用感についてフィードバックをくれたチームもありがとうございました!
XAION DATAはFastAPI以外にもモダンなフレームワークやサービスを活用して開発しております。前年比300%以上の成長を連続で達成しており、プロダクト拡大のため、AI x DATAを活用したWebシステムの開発に興味がある方をお待ちしております!
Meety
XAION DATA採用ページ
この記事が気に入ったらサポートをしてみませんか?