こんにちは、かつコーチです。
Djangoはstartappでいくらでもアプリを追加できますが、「何を1つのアプリにまとめるか」の基準を持たずに開発を進めると、後から手がつけられなくなります。
この記事では、Djangoプロジェクトのアプリ分割の考え方と、よくある失敗パターンを解説します。
アプリ分割で失敗しやすい2つの極端
1つの巨大アプリに全部詰め込むパターン
最も多い失敗が、myprojectのようなアプリ1つに、ユーザー機能・注文機能・レビュー機能まで全部詰め込むパターンです。
models.pyが数千行になり、git blameで誰がどこを変更したか追うのも困難になります。
Djangoのアプリは「機能的に凝集した単位」で分けるための仕組みであり、1つのアプリに全部詰め込むのは仕組みの利点を捨てているのと同じです。
機能を細かく分けすぎるパターン
逆に「モデル1つにつきアプリ1つ」のように分割しすぎるのも問題です。
apps/
users/
user_profiles/
user_settings/
user_notifications/
筆者は以前、ユーザー関連の機能を4つのアプリに分割したプロジェクトに途中参加したことがあります。
ユーザー登録処理を1つ直すだけで4つのアプリを横断してインポート関係を追う必要があり、かえって開発速度が落ちました。
アプリを跨いだ相互インポートは循環参照を起こしやすく、ImportError: cannot import nameのようなエラーの温床にもなります。
適切な分割単位の考え方
ドメイン(業務領域)でまとめる
分割の基準としてよく使われるのが、技術的な種類ではなく業務上のまとまり(ドメイン)で分ける方法です。
| 分割方針 | 例 |
|---|---|
| 技術種別で分ける(非推奨) | models_app views_app api_app |
| ドメインで分ける(推奨) | accounts(会員管理) orders(注文) catalog(商品) |
ECサイトであれば、「会員」「商品カタログ」「注文」「決済」のように、事業上の関心事の単位でアプリを切ると、機能追加のたびに触るアプリが自然と絞られます。
1アプリの目安は「独立してテストできる単位」
分割の判断に迷ったら、「このアプリだけを取り出して単体でテストできるか」を基準にすると判断しやすくなります。
他のアプリのモデルに強く依存しすぎている場合は、そのアプリの境界設定自体を見直すサインです。
アプリ間の依存関係を設計する
❌ Before:アプリ同士が双方向にimportし合う
# orders/models.py
from catalog.models import Product # OK:一方向の依存
# catalog/models.py
from orders.models import Order # NG:循環参照のリスク
catalogアプリがordersアプリに依存し、ordersアプリもcatalogアプリに依存すると、循環インポートが発生しやすくなります。
✅ After:依存の向きを一方向に揃える
# core/apps.txt(イメージ図)
catalog ← orders ← payments
(商品カタログは注文に依存しない。注文は決済に依存しない)
依存関係を図にしたとき、矢印が一方向に流れる状態を保つのが理想です。
どうしても双方向の連携が必要な場合は、Djangoのシグナル(モデルの保存など特定イベントをフックする仕組み)を使い、直接インポートを避ける方法もあります。
# payments/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from orders.models import Order
@receiver(post_save, sender=Order)
def create_payment_record(sender, instance, created, **kwargs):
if created:
Payment.objects.create(order=instance)
シグナルを使えば、ordersアプリ自体はpaymentsアプリの存在を知らないままにできます。
ただしシグナルは処理の流れが追いにくくなるデメリットもあるため、多用は禁物です。
共通機能の置き場所
commonアプリ・coreアプリの使い方
複数アプリで使う共通のモデル(例:TimeStampedModelという抽象基底クラス)やユーティリティは、commonやcoreという専用アプリにまとめます。
# common/models.py
class TimeStampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
# orders/models.py
from common.models import TimeStampedModel
class Order(TimeStampedModel):
status = models.CharField(max_length=20, default="pending")
commonアプリは他のどのアプリにも依存しない「最下層」に置くのが原則です。
commonが特定の業務アプリに依存し始めたら、それはもう共通機能ではなく、切り出し先を間違えているサインです。
まとめ
この記事のポイント
- 1アプリに全機能を詰め込む・細かく分けすぎる、どちらも極端で保守性を下げる
- 分割の基準は技術種別ではなく「業務ドメイン」でまとめること
- アプリ間の依存関係は一方向に揃え、循環参照を避ける
- 共通機能は他のアプリに依存しない
commonアプリに集約する
次に読むべき記事
設計・アーキテクチャ編を踏まえ、実際にDRFでシンプルなTodo APIを組み立てる実践編を次回お届けします。
→ 次の記事:Django REST FrameworkでシンプルなTodo APIを作ってみる