DjangoのようなMVT(Model-View-Template)アーキテクチャを採用するフレームワークを使い始めると、開発者はしばしば、あらゆるビジネスロジックを「ビュー(View)」関数(一般的に言う「コントローラー」)に詰め込んでしまうという過ちを犯しがちです。
この方法は、小規模な機能では手軽に感じるかもしれませんが、アプリケーションが成長するにつれて、コードは複雑怪奇になり、テストもメンテナンスも困難な「巨大なビュー(Fat View)」を生み出してしまいます。
今回は、この問題を解決するためのDjangoコミュニティにおける設計思想の金字塔、**「Fat Models, Thin Views(モデルは厚く、ビューは薄く)」**という原則について、具体的なコードのリファクタリングを通して解説します。
問題点:あらゆるロジックが混在した「Fat View」
ブログの記事一覧を表示するビューを考えてみましょう。このビューは、以下の機能を持っています。
- ブログの投稿者本人でなければアクセスできない
- 公開済みの記事のみを表示する
- タイトルによる検索機能がある
- 各記事のおおよその読了時間を表示する
これらの機能をすべてビューに詰め込むと、次のようなコードになります。
悪い例 (views.py
):
from django.shortcuts import get_object_or_404, render
from django.core.exceptions import PermissionDenied
from .models import Blog, Article
def article_list_view(request, blog_id):
# 1. データ取得と権限チェック
blog = get_object_or_404(Blog, id=blog_id)
if blog.author != request.user:
raise PermissionDenied("このブログの編集権限がありません。")
# 2. クエリの組み立て
articles = Article.objects.filter(blog=blog, is_published=True)
# 3. 検索フォームの処理とバリデーション
search_query = request.GET.get("query", "")
if search_query:
if len(search_query) < 2:
# バリデーションエラー処理
pass
articles = articles.filter(title__icontains=search_query)
# 4. ビジネスロジック(計算)と表示用データの加工
processed_articles = []
for article in articles:
word_count = len(article.body.split())
# 1分あたり400文字読むと仮定
reading_time = round(word_count / 400) or 1
processed_articles.append({
"title": article.title,
"reading_time": f"{reading_time}分",
})
# 5. レンダリング
return render(request, "articles/list.html", {"articles": processed_articles})
このビューは、権限チェック、DBクエリ、フォーム処理、ビジネスロジック、データ加工という、あまりに多くの責務を一人で抱え込んでいます。このような「Fat View」は、再利用性が低く、テストが非常に困難です。
解決策:ロジックを適切な層に分離する
「Fat Models, Thin Views」の原則に従い、これらのロジックを本来あるべき場所へ移動させていきましょう。
1. データベースロジックは「Model Manager」へ
「公開済みの記事を取得する」といった、繰り返し使われるクエリは、カスタムQuerySet
とManager
に定義します。
models.py
class ArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(is_published=True)
class Article(models.Model):
# ... (フィールド定義) ...
is_published = models.BooleanField(default=False)
objects = ArticleQuerySet.as_manager()
2. オブジェクト固有のロジックは「Modelプロパティ」へ
「記事の読了時間」のように、単一のオブジェクトに紐づくビジネスロジックは、モデルのメソッドやプロパティとして実装します。
models.py
class Article(models.Model):
# ... (フィールド定義) ...
body = models.TextField()
@property
def reading_time(self) -> int:
"""おおよその読了時間(分)を計算して返す。"""
word_count = len(self.body.split())
time = round(word_count / 400)
return time or 1 # 最低でも1分と表示
3. 入力値の検証と処理は「Form」へ
検索クエリの受け取りやバリデーション、そしてそれに基づいたフィルタリング処理は、django.forms
の責務です。
forms.py
from django import forms
class ArticleSearchForm(forms.Form):
query = forms.CharField(min_length=2, required=False)
def filter_queryset(self, queryset):
if self.is_valid():
query = self.cleaned_data.get("query")
if query:
return queryset.filter(title__icontains=query)
return queryset
4. 権限チェックは「ヘルパー関数」や「Mixin」へ
ブログの所有者かを確認するような汎用的な権限チェックは、独立した関数に切り出します。
permissions.py
from django.core.exceptions import PermissionDenied
def validate_author_permission(user, blog):
if blog.author != user:
raise PermissionDenied("このブログの編集権限がありません。")
5. 表示に関するロジックは「Template」へ
データの最終的な表示形式(例: 〇〇分
)は、テンプレート層で扱うのが適切です。
list.html
{% for article in articles %}
<p>{{ article.title }} (読了時間: 約{{ article.reading_time }}分)</p>
{% endfor %}
結果:責務が分離された「Thin View」
すべてのロジックを分離した結果、私たちのビューは驚くほどスリムで読みやすくなります。ビューの役割は、各コンポーネントを呼び出す「指揮者」に徹することです。
リファクタリング後の views.py
:
from django.shortcuts import get_object_or_404, render
from .models import Blog
from .forms import ArticleSearchForm
from .permissions import validate_author_permission
def article_list_view(request, blog_id):
# データ取得と権限チェック
blog = get_object_or_404(Blog, id=blog_id)
validate_author_permission(request.user, blog)
# Model Managerから基本となるクエリセットを取得
articles = blog.article_set.published()
# Formを使ってクエリの検証とフィルタリング
form = ArticleSearchForm(request.GET)
articles = form.filter_queryset(articles)
# レンダリング
context = {"blog": blog, "articles": articles, "form": form}
return render(request, "articles/list.html", context)
この「Thin View」は、何をしているかが一目瞭然で、各部品が独立しているため、テストも非常に容易です。
まとめ
Djangoでスケーラブルなアプリケーションを構築する鍵は、「Fat Models, Thin Views」の原則を徹底することです。
- Model: ビジネスロジックとデータベース関連の処理を担う。
- Form: ユーザー入力の検証と、それに関連する処理を担う。
- Template: データの表示方法を担う。
- View: これらをつなぎ合わせ、リクエストに応じて適切なレスポンスを返す「指揮者」に徹する。
ロジックを適切な場所に配置する習慣を身につけることで、コードの再利用性、テスト容易性、そして保守性を劇的に向上させることができます。