FastAPIで構築するモジュラーモノリス:大規模開発に耐えうるディレクトリ構成と依存性注入の実践

当ページのリンクには広告が含まれています。
🚀
AI・機械学習スキルを身につけるなら

お疲れ様です!IT業界で働くアライグマです!

結論から言います。FastAPIのプロジェクトが肥大化してきたとき、安易に「routersフォルダ」にファイルを詰め込むのはやめましょう。そして、早すぎるマイクロサービス化も避けるべきです。 今あなたに必要なのは、ドメインごとに境界を引いた「モジュラーモノリス」な構成です。

今回は、開発者が5人を超え、エンドポイントが50を超えてきたあたりで直面する「コードのスパゲッティ化」や「循環インポート」の問題を、ディレクトリ構成と依存性注入(DI)の工夫で解決する実践的なアーキテクチャを紹介します。

目次

なぜ今「モジュラーモノリス」なのか?

💡 年収800万以上の自社開発案件を探す
ハイクラス求人に特化したスカウトを受け取ろう

まずは、なぜ今、モノリスでもマイクロサービスでもなく「モジュラーモノリス」が注目されているのか、その背景を整理します。

  • モノリスの限界: 初期開発は早いが、コードが増えるにつれて影響範囲が見えなくなり、変更への恐怖が増大する。
  • マイクロサービスの罠: 理想的に見えるが、分散トランザクション、ネットワーク遅延、デプロイの複雑さなど、インフラ認知負荷が激増する。
  • モジュラーモノリスの利点: デプロイ単位は一つのモノリスでありながら、内部はモジュール単位で疎結合に保たれる。将来的な切り出しも容易。

詳しくは、Goで実装するヘッドレスワークフローエンジンの記事でも触れていますが、アーキテクチャ選定は「組織の認知負荷」を下げるために行うべきです。

IT女子 アラ美
マイクロサービスにするほどではないけど、今のままじゃ管理しきれない……という規模感にピッタリなんですね。

ITアライグマ
その通りです。特にPython/FastAPIはモジュール機能が強力なので、フォルダ構成を工夫するだけでかなり堅牢な設計が作れますよ。

ディレクトリ構成の戦略(Package by Feature)

FastAPIの公式チュートリアルなどでは、機能ごとではなくレイヤーごと(routers/, models/, schemas/)に分ける構成がよく見られますが、中規模以上では「Package by Feature(機能による分割)」を推奨します。

以下は、ユーザー管理と決済機能を別モジュールとして切り出したディレクトリ構成例です。


src/
├── main.py            # アプリケーションのエントリーポイント
├── modules/           # 各ドメインモジュールを格納
│   ├── user/          # ユーザー管理ドメイン
│   │   ├── __init__.py
│   │   ├── router.py  # エンドポイント定義
│   │   ├── service.py # ビジネスロジック
│   │   ├── schema.py  # Pydanticモデル
│   │   └── model.py   # DBモデル
│   └── payment/       # 決済ドメイン
│       ├── __init__.py
│       ├── router.py
│       └── ...
└── core/              # 共通基盤(設定、DB接続、Middlewareなど)
    ├── config.py
    └── database.py

このように関連ファイルを一箇所に集約することで、例えば「ユーザー登録機能の修正」を行う際に、routersmodelsを行ったり来たりする必要がなくなります。

関連する設計思想として、Remix 3の新コンポーネントライブラリ入門でも語った「コロケーション(関連するものを近くに置く)」の考え方がサーバーサイドでも有効です。

IT女子 アラ美
なるほど。機能ごとまとまっていると、そのフォルダを見るだけで何をしているかが分かりやすいですね。

ITアライグマ
そうなんです。そして重要なのは、「他のモジュールのModelやServiceを勝手にimportしない」というルールを設けることです。

実装ステップ1:Routerの分割とDI(Dependency Injection)

それでは、具体的な実装を見ていきましょう。まずは各モジュールで定義したRouterを、アプリケーション全体に統合する方法です。

最も重要なのは、Routerを定義するファイル内でビジネスロジックを直接書かないことです。FastAPIのDIシステムを活用して、Service層を注入します。


# src/modules/user/router.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from src.core.database import get_db
from src.modules.user.service import UserService
from src.modules.user.schema import UserCreate, UserResponse

# モジュール専用のRouterを定義
router = APIRouter(prefix="/users", tags=["users"])

# DI用の依存関数
def get_user_service(db: Session = Depends(get_db)) -> UserService:
    return UserService(db)

@router.post("/", response_model=UserResponse)
def create_user(
    user_in: UserCreate,
    service: UserService = Depends(get_user_service)
):
    # RouterはHTTPリクエストのハンドリングに徹し、ロジックはServiceに委譲
    return service.create_user(user_in)

そして、main.py でこれらを集約します。


# src/main.py
from fastapi import FastAPI
from src.modules.user.router import router as user_router
from src.modules.payment.router import router as payment_router

app = FastAPI(title="Modular Monolith API")

# 各モジュールのRouterを登録
app.include_router(user_router)
app.include_router(payment_router)

こうすることで、main.py は各モジュールの詳細を知る必要がなくなり、「ルーティングの登録台帳」としての責務に集中できます。

依存性注入の考え方については、jotaiで学ぶReact Suspense時代の状態管理などのフロントエンド設計記事とも通底する「関心の分離」が重要です。

IT女子 アラ美
Routerがスッキリしました!これならテストの時もServiceをモック(偽装)しやすそうですね。

ITアライグマ
ご名答です。Depends(get_user_service) をオーバーライドするだけで、DB接続なしでコントローラー層のテストが可能になりますよ。

実装ステップ2:モジュール間の境界線(Inter-module Communication)

モジュラーモノリスの要諦は「境界」です。ここで厳守すべき唯一のルールは、「他のモジュールのDBモデル(SQLAlchemyのModel)を直接importしてはならない」ということです。

例えば、paymentモジュールからuserテーブルの情報が必要な場合、src.modules.user.model.User をimportしてクエリを書くのはNGです。これは将来的な切り出しを不可能にします。

代わりに、必ずPublicなServiceメソッド経由でデータを取得します。


# src/modules/payment/service.py
from src.modules.user.service import UserService # Service層への依存はOK
# from src.modules.user.model import User      # Model層への依存はNG!

class PaymentService:
    def __init__(self, db: Session, user_service: UserService):
        self.db = db
        self.user_service = user_service

    def process_payment(self, user_id: int, amount: int):
        # ユーザー情報の取得はUserモジュールに任せる
        user = self.user_service.get_user_by_id(user_id)
        if not user.is_active:
            raise ValueError("Inactive user")
        # ...決済処理

このように「インターフェース(Serviceメソッド)」に対して依存させることで、将来的にUserモジュールがマイクロサービスとして切り出され、gRPC通信に変わったとしても、UserServiceの内部実装を書き換えるだけで呼び出し側の変更を最小限に抑えられます。

この考え方は、エンジニアからEMへの転身で語られる「組織の境界線設計」とも似ています。API(インターフェース)さえ守られていれば、チームの内部実装には干渉しない、というマネジメントの原則と同じです。

IT女子 アラ美
少しコード量は増えますが、スパゲッティコードで深夜残業するよりマシですね……。

ITアライグマ
その通り。最初の少しの手間が、半年後のデスマーチを防ぐ「防波堤」になるんです。

実装後の効果検証(ケーススタディ)

💡

「設計力」を評価してくれる企業へ
年収1,000万円以上のテックリード案件多数

実際に、50万行規模のPythonプロジェクトでの導入事例を紹介します。初期は完全なモノリスでしたが、開発者数が5名から15名に急増したタイミングでモジュラーモノリスへ移行しました。

状況(Before)

  • 循環参照地獄: userorderpaymentが互いのModelをimportし合い、ファイルを保存するたびにImportErrorが発生。
  • デプロイの恐怖: 決済機能の修正が、なぜかユーザー認証機能にバグを生むという「謎の結合」が発生しており、リリース前のQAに3日を要していた。

行動(Action)

  • ディレクトリ隔離: まずは物理的にファイルを移動し、src/modules/ 配下に強制的に配置。
  • Linterによる監視: flake8-import-orderなどのツール設定に加え、CIで「他モジュールのModel import」をgrepして失敗させるスクリプトを導入し、物理的にルール違反を防いだ。

結果(After)

  • 開発速度の向上: モジュール単位で自律的に開発ができるようになり、機能追加のリードタイムが約半分に短縮。
  • 心理的安全性の確保: 「ここを触ってもあっちは壊れない」という保証が生まれたことで、リファクタリングが活発化した。

こうした環境改善の積み重ねは、年収が下がっても成長企業を選ぶ意義でも語ったように、エンジニアとしての本質的な生産性を高めるための重要な投資です。

開発規模拡大時の生産性スコア比較
図:規模拡大時の生産性比較(モジュラーモノリスの優位性)

IT女子 アラ美
ツールでルールを強制するのは良いアイデアですね!人間はすぐ楽な方に流れますから(笑)。

ITアライグマ
はい。アーキテクチャは「お気持ち」ではなく「仕組み」で守るのが鉄則です。

さらなる実践・活用に向けて

モジュラーモノリスが安定してきたら、次は「各モジュール内でのレイヤードアーキテクチャ」を深堀りしてみましょう。

今回はService層とModel層の簡易的な分離でしたが、より複雑なドメインでは「Repositoryパターン」や「Domain Modelパターン」をモジュール内部に適用することで、さらに堅牢性が高まります。また、非同期処理が必要な場合はTransactional Outboxパターンを組み合わせるのも効果的です。

さらなる年収アップやキャリアアップを目指すなら、ハイクラス向けの求人に特化した以下のサービスがおすすめです。

比較項目 TechGo レバテックダイレクト ビズリーチ
年収レンジ 800万〜1,500万円ハイクラス特化 600万〜1,000万円IT専門スカウト 700万〜2,000万円全業界・管理職含む
技術スタック モダン環境中心 Web系に強い 企業によりバラバラ
リモート率 フルリモート前提多数 条件検索可能 原則出社も多い
おすすめ度 S技術で稼ぐならここ A受身で探すなら Bマネジメント層向け
公式サイト 無料登録する - -
IT女子 アラ美
年収を上げたいんですが、ハイクラス求人ってハードルが高そうで迷います…
ITアライグマ
技術力を武器に年収を上げたいならTechGo一択!でも、自分の市場価値を幅広くチェックしたいならビズリーチも登録しておくと安心ですよ。

まとめ

モジュラーモノリスは、FastAPIのような現代的なフレームワークと非常に相性が良いアーキテクチャです。

  • ディレクトリを機能単位で切る: Package by Featureを徹底する。
  • 境界を守る: 他モジュールのDBモデルに直接触らない。RouterでDIを活用する。
  • 小さく始める: まずは一つの機能(例:通知機能)だけをモジュール化して切り出してみる。

「マイクロサービスはまだ早い、でもモノリスは辛い」。そんな成長期のチームにとって、モジュラーモノリスは現実的かつ最強の解です。明日からまずはディレクトリを1つ作って、ファイルを移動させることから始めてみてください。

IT女子 アラ美
さっそく明日の朝会で、ディレクトリ構成の変更を提案してみます!

ITアライグマ
その一歩がチームの未来を救います。応援していますよ!

厳しめIT女子 アラ美による解説ショート動画はこちら

この記事をシェアする
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ITアライグマのアバター ITアライグマ ITエンジニア / PM

都内で働くPM兼Webエンジニア(既婚・子持ち)です。
AIで作業時間を削って実務をラクにしつつ、市場価値を高めて「高年収・自由な働き方」を手に入れるキャリア戦略を発信しています。

目次