Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Djangoモノリスからのサービス分割:FastAPIへの移行

 Djangoモノリスからのサービス分割:FastAPIへの移行

Avatar for sara.ohtani.mt2

sara.ohtani.mt2

September 09, 2026

More Decks by sara.ohtani.mt2

Other Decks in Programming

Transcript

  1. 自己紹介 • 大谷 紗良(Sara Ohtani) • RevComm Inc. ◦ Software

    Engineer • @sara_ohtani_mt2 バックエンド開発とか組織づくりとか 2 Copyright © RevComm Inc.
  2. プロジェクト概要 2023年5月スタート。段階的に切り替え・拡張していて、現在も一部続いています 2023.05 2023.12 2024〜 現在 プロジェクトスタート チーム立ち上げ 移行の第一段階リリース (app分割)

    段階的に切り替え・拡張 DB分割は継続中… メンバーは入れ替わりつつ、2〜6人 大谷はバックエンドエンジニアとして参加 Copyright © RevComm Inc.
  3. どんなサービス? ビジネス音声コミュニケーション 録音・録画 気づき 電話 ビッグデータ化・資産化 商談の蓄積 商談状況の把握・共有 商談成功・失敗要因の特定 業務の効率化

    Web会議 文字起こし トーク解析 感情認識 出現単語 トレンド分析 ダッシュボード(統計表示) 対面での 会議 7 AI解析 生成AI 議事録作成 議事録の自動作成 営業活動の分類・共有 コミュニケーションスキル向上 スキルレベルの可視化 セルフコーチングによる成長 Copyright © RevComm Inc.
  4. どこで分割? ビジネス音声コミュニケーション 録音・録画 気づき 電話 ビッグデータ化・資産化 商談の蓄積 商談状況の把握・共有 商談成功・失敗要因の特定 業務の効率化

    Web会議 文字起こし トーク解析 感情認識 出現単語 トレンド分析 ダッシュボード(統計表示) 対面での 会議 24 AI解析 生成AI 議事録作成 議事録の自動作成 営業活動の分類・共有 コミュニケーションスキル向上 スキルレベルの可視化 セルフコーチングによる成長 Copyright © RevComm Inc.
  5. どこで分割? 連絡先管理機能 ビジネス音声コミュニケーション 録音・録画 気づき 電話 ビッグデータ化・資産化 商談の蓄積 商談状況の把握・共有 商談成功・失敗要因の特定

    業務の効率化 Web会議 文字起こし トーク解析 感情認識 出現単語 トレンド分析 ダッシュボード(統計表示) 対面での 会議 25 AI解析 生成AI 議事録作成 議事録の自動作成 営業活動の分類・共有 コミュニケーションスキル向上 スキルレベルの可視化 セルフコーチングによる成長 Copyright © RevComm Inc.
  6. 連絡先管理機能 • 比較的シンプルな機能だった ◦ 会社名・担当者名・電話番号・カテゴリーの管理 ◦ 発信・着信時などに名前を表示したり ◦ CRMではない •

    他機能への影響が小さかった ◦ 参照 ◦ 障害発生時の他機能への影響 • パフォーマンス面で課題があり、リニューアルしたかった ◦ レスポンススピードUP ◦ 登録可能上限数UP 26 Copyright © RevComm Inc.
  7. 連絡先管理機能 • 比較的シンプルな機能だった ◦ 会社名・担当者名・電話番号・カテゴリーの管理 ◦ 発信・着信時などに名前を表示したり ◦ CRMではない •

    他機能への影響が小さかった ◦ 参照 ◦ 障害発生時の他機能への影響 • パフォーマンス面で課題があり、リニューアルしたかった ◦ レスポンススピードUP ◦ 登録可能上限数UP これ自体を特に分割したいというよりは社内の先行事例にしやすかった 27 Copyright © RevComm Inc.
  8. 構成 Before / After Before 連絡先テーブルを直接参照 Client Django Client Django

    PostgreSQL After 連絡先管理API PostgreSQL ▲ ここが増えた Djangoから連絡先テーブルへの直接参照はなくなり、連絡先APIを経由するようになった ※ Django自身のDB接続は、連絡先以外のテーブル向けに残っている Copyright © RevComm Inc.
  9. フレームワーク選定 選択肢A:Djangoのまま切り出す 選択肢B:別のフレームワークへ移行 • 元コードからの変更が少なくできる • 移行コストがかかる • 多機能すぎて連絡先には不要な機能がある •

    必要な機能に絞って軽量化できる • 既存のものより速くするのは難しい 35 • 高パフォーマンスを強みとしている Copyright © RevComm Inc.
  10. フレームワーク選定 選択肢A:Djangoのまま切り出す 選択肢B:別のフレームワークへ移行 • 元コードからの変更が少なくできる • 移行コストがかかる • 多機能すぎて連絡先には不要な機能がある •

    必要な機能に絞って軽量化できる • 既存のものより速くするのは難しい • 高パフォーマンスを強みとしている FastAPI × SQLAlchemy 36 Copyright © RevComm Inc.
  11. FastAPI以外の選択肢と学習コスト • Flaskなどの他のフレームワークはほぼ検討しなかった • 学習コストは会社全体を対象に考える ◦ 弊社の場合はチーム間の人移動がカジュアルにある ◦ 社内で導入事例があると具体的に相談もしやすい ◦

    弊社では他のチームでFastAPIを利用している実績があった • 現在はGoなども一部で導入しているが、この2023年時点では未導入 ◦ 新規での開発ではなく、提供済み機能の分割移行なので 新言語の採用は避けた 38 Copyright © RevComm Inc.
  12. FastAPIでよくあるディレクトリ構成 ディレクトリ構成の“公式”はない。Djangoのstartappコマンドのようなものもない Type別構成 app/ ├── routers/ ├── models/ ├── schemas/

    └── services/ 技術的な種類でディレクトリを分ける ドメイン別構成 app/ ├── contacts/ │ ├── router.py │ ├── models.py │ └── schemas.py └── users/ 機能単位で分ける。将来のサービス分割と相性が良い Copyright © RevComm Inc.
  13. 今回はあえて移行前の構成に寄せた Django時代の views / models / serializers の分離をベースに構成 Django(移行前) src/{project}/

    ├── views/api/{domain}/v1.py ├── models.py └── serializers/ FastAPI(移行後) app/ ├── domain/values/ ├── models/ ├── repositories/ ├── serializers/ ├── use_cases/{domain}/ └── views/ 結果的にクリーンアーキテクチャ寄りの構成に Copyright © RevComm Inc.
  14. Django→FastAPI書き換え例① 同期 / 非同期 WSGIの同期前提から、ASGIの async / await 前提へ Django:同期

    FastAPI:非同期 def get_all(request): records = BusinessContact.objects.filter(...) total = records.count() # すべて同期呼び出し async def execute(cls, tenant_id, phone_number): async with async_db_session(...) as session: result = await IsvizContactRepository() .filter_in_phone_numbers(session=session, ...) WSGI・同期が前提 ASGI・非同期が前提 書き方はシンプル async def / async with / await を一貫させる 1つ同期を混ぜるとイベントループを塞ぐ Copyright © RevComm Inc.
  15. Django→FastAPI書き換え例② リクエストの型付けとバリデーション 検証コードを自分で呼び出す形から、型ヒントだけで配線まで揃う形へ Django:手動配線 class ContactSerializer(serializers.Serializer): name = serializers.CharField() phone_number

    = serializers.CharField() def create_contact(request): s = ContactSerializer(data=request.data) s.is_valid(raise_exception=True) data = s.validated_data 宣言的なSerializerは書けるが、 is_validを呼び出す配線は自分で書く 呼び忘れると検証されないまま処理が進む FastAPI:配線も自動 class ContactItem(BaseModel): name: str | None phone_number: str | None @field_validator("name", mode="before") def trim_space(cls, v: str) -> str: return v.strip() 型ヒントで検証・パース・エラー形式生成 APIドキュメントも自動生成 Copyright © RevComm Inc.
  16. ハマり①:原因は add → flush → commit の流れ execute() の返り値からIDを取ろうとしたが、flush() 前なのでIDがまだ採番されていなかった

    add() flush() セッションに登録するだけ SQLはまだ発行されない SQLが発行され PKが採番される commit() トランザクション確定 SQLAlchemy Django ORM 3段階。flush 前は ID が None save() で即DB反映。この感覚で書いていた Copyright © RevComm Inc.
  17. ハマり①:NG と OK execute() の返り値ではなく、add() したインスタンス自身から ID を取る NG #

    NG: execute() の返り値からIDを取ろうとする result = await session.execute( insert(Category).values(name="新規カテゴリ") ) category_id = result.inserted_primary_key # → この時点ではflushが反映されておらず # None になる OK # OK: add() したインスタンス自身から取る new_category = Category(name="新規カテゴリ") session.add(new_category) await session.flush() # ↑ ここでSQLが発行され、PKが採番される category_id = new_category.id # → インスタンスに直接反映されている Copyright © RevComm Inc.
  18. ハマり②:NG と OK insert(...).values(**data) の形にすると write と認識される NG OK #

    NG: values() を使っていない await session.execute( insert(Contacts).returning(Contacts), insert_contact_data, ) # OK: insert() で values() を使っている await session.execute( insert(Contacts) .values(**insert_contact_data) .returning(Contacts) ) # → Error cannot execute INSERT in a read-only transaction 「values を使っているか」に気をつけ続けるのはつらい → session.add() へ Copyright © RevComm Inc.
  19. 結論:session.add() を使う _flushing で確実に write 判定される。ORM のオブジェクト関係に任せてまとめて add() できる session.add()

    new_category = Category(name="新規カテゴリ") new_contact = Contacts(name="山田", phone_number="03...") new_contact.categories.append(new_category) session.add(new_contact) await session.flush() await session.commit() # カテゴリーもまとめて # write 判定 → primary add() のメリット • _flushing で確実に write 判定 • values を使っているか気をつけなくてよい • ORMのオブジェクト関係に任せて まとめて add()できる • ハマり①の問題のためにもadd()の方がいい Copyright © RevComm Inc.
  20. 残る課題 アプリを分割したことについては、やった甲斐があった DBの分割はまだこれからなので手探り • 他のDBと紐づけたいデータが多いとつらそう ◦ 現状は多くはないが・・・ • 分割したらテーブルの再設計もしたい ◦

    色々と要望があるので、それに対応できるものにしていきたい ◦ 連絡先、奥が深い・・・(会社と担当者の紐づけ、電話番号、email…) このプロジェクトを参考にビデオ会議のAPIを分割するプロジェクトも進行中 63 Copyright © RevComm Inc.