🚀

Devinを活用したFlaskからFastAPIへの移行でうまく行ったことといかなかったこと

に公開

はじめに

こんにちは!mento のエンジニアのよしけんです。

現在 mento では、マネジメントサクセスプラットフォームの立ち上げに取り組んでいます。その中で、既存の Flask アプリケーションを FastAPI に移行するプロジェクトを進めており、この移行作業で Devin を積極的に活用しました。

本記事では、Flask から FastAPI への移行プロジェクトで Devin を活用した経験から、「うまく行ったこと」と「うまくいかなかったこと」について、実際の PR を交えながらお伝えします。

同様にフレームワーク移行や AI ツールを活用した開発を検討している方の参考になれば幸いです。

モチベーション

mento では上述のように、新事業としてマネジメントサクセスプラットフォームの立ち上げを進めています。

マネジメントという領域は 1 つのソリューションで解決が難しく、複数のプロダクトを開発することが必要になることが見えていました。


mento CompanyDeckより

そのため、今後マイクロサービス化することを見越したモジュラモノリスなアーキテクチャの採用を決めました。

モジュラモノリスアーキテクチャを採用することで、各プロダクトの独立性を保ちながら、共通機能の再利用や運用の効率化を図ることができます。

元々は既存事業(コーチングプラットフォーム)用に Flask でのモノリスなアプリケーションが動いていましたが、このタイミングで FastAPI への移行も行うこととしました。

FlaskよりもFastAPIを選んだ理由

既存の Flask アプリケーションから FastAPI への移行を決定した主な理由は以下の通りです↓

開発が活発でドキュメントも読みやすい

FastAPI は 2018 年にリリースされ、直近のリリース頻度も月 1 以上で開発が活発でした。

ドキュメントもわかりやすく充実していました。

非同期処理やスキーマ駆動開発のサポートなどの機能差分

また、機能としても大きく非同期処理のネイティブサポートや Pydantic を利用したスキーマ駆動開発のサポートなど、今後の大規模開発に備えた嬉しい機能がありました。

Flask は軽量なフレームワークで扱いやすいため、mento の初期に採用しました。
Flask 自体に大きな不満があったわけではないですが、直近のリリース頻度が年に 2,3 回で少し下火になっていたところもあり、そのまま Flask に乗っていくことに不安がありました。

移行コストと見比べてメリットが多いと判断し、このタイミングで移行することを決めました。

計画

移行作業を進めるにあたって、既存アプリケーションの開発をなるべく止めたくなかったので、以下のような段階的な移行計画を立てました。

フェーズ1: コードフリーズなしで進められるところを進める移行スクリプトを書く

  • 既存の Flask アプリケーションに影響を与えない範囲で、Flask への依存を剥がす。
  • 移行の際に Flask の書き方を FastAPI に変換する Python スクリプトの準備。

フェーズ2: コードフリーズと開始移行スクリプトの実行

  • 準備した移行スクリプトを実行して、実際の移行作業を開始。
  • スクリプトで漏れた部分の修正。

フェーズ3: 場当たり的に対応が必要なところの作業

  • 移行後に発見された問題や、自動化できなかった部分の手動対応。

この手順により、コードフリーズ期間を短く実装していくことができました。
移行には繰り返し作業が多いため、AI をフル活用していく必要がありました。

mentoでのDevinの活用

現在 mento では、業務委託の方も含めて Devin を積極的に活用しています。
Devin 以外にも Cursor などの AI ツールも活用していますが、今回の移行プロジェクトで一番使っていたのは Devin なので、本記事では Devin に焦点を当てて紹介します。

Devin を使うと、Slack から命令できて、生成物をプルリクエストにしてくれるので

  • 変更内容を具体的に確認できる。
  • チームメンバーとのレビューがしやすい。
  • 変更の影響範囲を把握しやすい。
  • 必要に応じて部分的に採用できる。
  • できる並列で実行できる。

というメリットがあり、今回の移行作業でも積極活用することとしました。

ここからはうまく行ったことといかなかったことについてシェアできればと思います。

Devinがうまく行ったこと

事前に計画を立てるために移行をまず全部やったらどうなるかを何回かトライしてもらう

移行作業の計画を立てるために、まず Devin に「実際に全部移行をやってみる」ことをお願いしました。
実際にこの PR を採用することはないですが、これにより、移行に必要な作業の全体像を把握できました。

これらの PR を通じて、移行に必要な修正範囲を洗い出すことができました。

コードフリーズ期間を短くするためのスクリプトを実装してもらう

移行作業によるサービス停止時間を最小限に抑えるため、Devin に移行を自動化するスクリプトの実装をお願いしました。

実際のコントローラーファイル変換スクリプト(一部)

#!/usr/bin/env python3
import argparse
import os
import re
from pathlib import Path


def convert_blueprint_to_router(content: str) -> str:
    """
    Blueprintの定義をAPIRouterに変換する
    例: bp = Blueprint("admins/clients", __name__, url_prefix="/admins")
    -> client_router = APIRouter(prefix="/admins")
    """
    # Blueprint定義を検出するための正規表現
    # url_prefixがあるパターン
    blueprint_with_prefix_pattern = (
        r'(\w+)\s*=\s*Blueprint\s*\(\s*["\']([^"\']+)["\'].*?url_prefix\s*=\s*["\']([^"\']+)["\'].*?\)'
    )
    # url_prefixがないパターン
    blueprint_without_prefix_pattern = r'(\w+)\s*=\s*Blueprint\s*\(\s*["\']([^"\']+)["\'].*?\)'

    # url_prefixありのパターンを試す
    match = re.search(blueprint_with_prefix_pattern, content, re.DOTALL)
    ...
    ...
    ...

なかなかこれらのスクリプトを自分たちで用意するのは骨が折れます。

これらのスクリプトにより、手動での移行作業を大幅に削減し、コードフリーズ期間を短縮できました。

繰り返し作業の実行

スクリプトで漏れていた繰り返し作業も AI がうまく活用できました。

具体例:認証デコレーターの一括変換

ほぼ全てのエンドポイントで同じパターンの変更が必要でしたが比較的単純だったため一括で修正ができました。

@posts_router.put("/{post_id}")
- @authentication_required
- def update_post(post_id: str, request: Request) -> JSONResponse:
+ def update_audio(post_id: str, request: Request, _: None = Depends(authentication_required)) -> JSONResponse:
    ...

コントローラーテストのカバレッジ向上

今回のフレームワーク移行では認証デコレータやパスの記法、Pydantic によるリクエストの型の指定など、ほぼ全てのコントローラーに影響がありました。

テストカバレッジが 100%ではなかったため、また一部破壊的な変更となるため影響範囲が見えきらないと不安が残りました。
そのため、Devin に追加のテストを実装してもらうこととしました。

移行後のテストの書き方に合わせて足りない部分を列挙してテストを追加してもらう方法だったので Devin でも対応しやすく、コントローラー層のテストカバレッジをほぼ 100%にできました。

うまくいかなかったこと

コントローラー+テストの一括修正のような複雑な変更依頼の失敗

リクエストデータの Pydantic モデルに加えてテストも合わせて修正する時など複数の変更を同時に行いたい場合には、修正方針がほぼ一意に決まる場合でもうまくいかず、人間が(Cursor などを使い)修正することとなりました。

これくらいの修正でもテスト修正と合わせて依頼するといちうまくいきませんでした。
例えば、本当はリクエストクラスをエンドポイントのすぐ上に書いて欲しいのにそれを無視したり、使わないで欲しいと伝えても非同期関数を使ってしまう、勝手に既存の実装を削除してしまうなどが起きてしまい修正を行う必要がありました。

+ class AddPostRequest(BaseModel):
+     id: str


@cards_router.post("", response_model=None)
@login_required
- def add_post(request: Request) -> JSONResponse:
+ def add_post(request: Request, add_post_request: AddCardRequest) -> JSONResponse:
    ...

そのため、適宜分割して依頼したり、人間の手で修正したりする必要がありました。

ハルシネーションによる型のミスでValidationエラー

FastAPI は Pydantic を使用した型チェックを行うため、型の定義にミスがあると Validation エラーが発生します。

str とすべきところを int にしてしまったりしていて、POST リクエスト時などにエラーになるケースがあることに気がつき、AI によるチェックだけだと不安が残ったため最終的には人間が全てチェックすることとなりました。

# ❌ Devinが生成した問題のあるモデル
class UserCreateRequest(BaseModel):
    id: str  # intが正しい

元々API リクエスト・レスポンスのスキーマが定義されていなかったこともあり、チェックには時間がかかってしまいました。

大事なところでのハルシネーションは AI を用いた実装で怖い部分ですね。

まとめ

Flask から FastAPI への移行プロジェクトで Devin をメインで使ってうまく行ったことといかなかったことを紹介しました。

自動化スクリプトを書かせてコードフリーズ期間を短縮できたのは体験として良かったですが、全体を通してまだ人間の手を使ってしまったなという印象でした。

移行完了してから Claude Code なども出てきていますし、mento でも AI のためのドキュメントを大量生産していたりするので今後どんどん精度が上がっていくと思います。
AI ツールを活用しながら、より効率的で品質の高い開発を進めていきたいと思います!

お知らせ

mento では、新事業・プロダクトの立ち上げに一丸となって取り組むエンジニアの仲間を募集しています!

少しでも興味がある方、ぜひ最初はカジュアルにお話しさせてください!(各種 SNS でも DM いただければすぐ反応します!)

https://herp.careers/v1/mento/zgEhIVY4CLhN

Discussion