お疲れ様です!IT業界で働くアライグマです!
結論から言います。FastAPIのプロジェクトが肥大化してきたとき、安易に「routersフォルダ」にファイルを詰め込むのはやめましょう。そして、早すぎるマイクロサービス化も避けるべきです。 今あなたに必要なのは、ドメインごとに境界を引いた「モジュラーモノリス」な構成です。
今回は、開発者が5人を超え、エンドポイントが50を超えてきたあたりで直面する「コードのスパゲッティ化」や「循環インポート」の問題を、ディレクトリ構成と依存性注入(DI)の工夫で解決する実践的なアーキテクチャを紹介します。
なぜ今「モジュラーモノリス」なのか?
まずは、なぜ今、モノリスでもマイクロサービスでもなく「モジュラーモノリス」が注目されているのか、その背景を整理します。
- モノリスの限界: 初期開発は早いが、コードが増えるにつれて影響範囲が見えなくなり、変更への恐怖が増大する。
- マイクロサービスの罠: 理想的に見えるが、分散トランザクション、ネットワーク遅延、デプロイの複雑さなど、インフラ認知負荷が激増する。
- モジュラーモノリスの利点: デプロイ単位は一つのモノリスでありながら、内部はモジュール単位で疎結合に保たれる。将来的な切り出しも容易。
詳しくは、Goで実装するヘッドレスワークフローエンジンの記事でも触れていますが、アーキテクチャ選定は「組織の認知負荷」を下げるために行うべきです。
IT女子 アラ美ディレクトリ構成の戦略(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
このように関連ファイルを一箇所に集約することで、例えば「ユーザー登録機能の修正」を行う際に、routersとmodelsを行ったり来たりする必要がなくなります。
関連する設計思想として、Remix 3の新コンポーネントライブラリ入門でも語った「コロケーション(関連するものを近くに置く)」の考え方がサーバーサイドでも有効です。



実装ステップ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時代の状態管理などのフロントエンド設計記事とも通底する「関心の分離」が重要です。



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(インターフェース)さえ守られていれば、チームの内部実装には干渉しない、というマネジメントの原則と同じです。



実装後の効果検証(ケーススタディ)
実際に、50万行規模のPythonプロジェクトでの導入事例を紹介します。初期は完全なモノリスでしたが、開発者数が5名から15名に急増したタイミングでモジュラーモノリスへ移行しました。
状況(Before)
- 循環参照地獄:
userとorderとpaymentが互いのModelをimportし合い、ファイルを保存するたびにImportErrorが発生。 - デプロイの恐怖: 決済機能の修正が、なぜかユーザー認証機能にバグを生むという「謎の結合」が発生しており、リリース前のQAに3日を要していた。
行動(Action)
- ディレクトリ隔離: まずは物理的にファイルを移動し、
src/modules/配下に強制的に配置。 - Linterによる監視:
flake8-import-orderなどのツール設定に加え、CIで「他モジュールのModel import」をgrepして失敗させるスクリプトを導入し、物理的にルール違反を防いだ。
結果(After)
- 開発速度の向上: モジュール単位で自律的に開発ができるようになり、機能追加のリードタイムが約半分に短縮。
- 心理的安全性の確保: 「ここを触ってもあっちは壊れない」という保証が生まれたことで、リファクタリングが活発化した。
こうした環境改善の積み重ねは、年収が下がっても成長企業を選ぶ意義でも語ったように、エンジニアとしての本質的な生産性を高めるための重要な投資です。





さらなる実践・活用に向けて
モジュラーモノリスが安定してきたら、次は「各モジュール内でのレイヤードアーキテクチャ」を深堀りしてみましょう。
今回はService層とModel層の簡易的な分離でしたが、より複雑なドメインでは「Repositoryパターン」や「Domain Modelパターン」をモジュール内部に適用することで、さらに堅牢性が高まります。また、非同期処理が必要な場合はTransactional Outboxパターンを組み合わせるのも効果的です。
さらなる年収アップやキャリアアップを目指すなら、ハイクラス向けの求人に特化した以下のサービスがおすすめです。
| 比較項目 | TechGo | レバテックダイレクト | ビズリーチ |
|---|---|---|---|
| 年収レンジ | 800万〜1,500万円ハイクラス特化 | 600万〜1,000万円IT専門スカウト | 700万〜2,000万円全業界・管理職含む |
| 技術スタック | モダン環境中心 | Web系に強い | 企業によりバラバラ |
| リモート率 | フルリモート前提多数 | 条件検索可能 | 原則出社も多い |
| おすすめ度 | 技術で稼ぐならここ | A受身で探すなら | Bマネジメント層向け |
| 公式サイト | 無料登録する | - | - |



まとめ
モジュラーモノリスは、FastAPIのような現代的なフレームワークと非常に相性が良いアーキテクチャです。
- ディレクトリを機能単位で切る:
Package by Featureを徹底する。 - 境界を守る: 他モジュールのDBモデルに直接触らない。RouterでDIを活用する。
- 小さく始める: まずは一つの機能(例:通知機能)だけをモジュール化して切り出してみる。
「マイクロサービスはまだ早い、でもモノリスは辛い」。そんな成長期のチームにとって、モジュラーモノリスは現実的かつ最強の解です。明日からまずはディレクトリを1つ作って、ファイルを移動させることから始めてみてください。












