綴Photo の勉強ノートに戻る
開発記録2026.06.18

Django を『材料・料理・配膳』に分けたら、view が読めるようになった

Django設計綴Photo

綴Photo のバックエンドを Django(Pythonのバックエンド用フレームワーク)と DRF(Django REST Framework。APIを作る追加部品)で組むときに、selector / service / view を分けた話。勉強のため。

view に「DB からどう取るか」「どれを選ぶか」「どう返すか」を全部書くと、一つの関数が何でも知っている状態になります。そこを3つの係に分けたら、view が驚くほど薄くなった。

3つの係に分ける

頭の中ではこう置いている。selector が材料を集め、service が料理し、view が配膳します。

行は PostgreSQL に残る。選んだ1枚も JSON もメモリで、保存しない。selector 材料読むだけ。判断しないQuerySet はメモリ交差。置き場所ではない組み合わせられる部品service 料理スキップ少・古い順で1枚1枚はメモリ。保存しない交差。置き場所ではない判断はここに一極集中view 配膳受け取って JSON を返すJSON はメモリ。保存しない交差。置き場所ではない中身には口を出さない読むだけ。表には書かないPostgreSQL(閉じても残る)photos_photo写真の本体photos_photostatusskip_count / last_skipped_atphotos_meaning綴りが無いものだけ対象

「次に意味づけする写真を1枚出す」をこの3つに割るとこうなります。

python
# selector(材料を集める): 読むだけ・更新も判断もしない
def get_unmeaning_active_photos(user):
    return (
        Photo.objects
        .filter(
            owner=user,
            meanings__isnull=True,  # 綴り(meanings)がまだ無い写真
        )
        .filter(Q(status__isnull=True) | Q(status__state=PhotoStatus.State.ACTIVE))
        # 状態がまだ無い写真も対象に含める
        .select_related("status")  # status を JOIN で一緒に取る(N+1 を防ぐ)
    )
python
# service(料理する): どれを選ぶか=アプリのルールはここだけ
def pick_next_photo(user):
    return (
        get_unmeaning_active_photos(user)
        .order_by(
            "status__skip_count",      # スキップ数が少ない写真を優先
            "status__last_skipped_at", # 同数なら最後にスキップした日が古い順
            "created_at"               # それでも同じなら作成が古い順
        )
        .first()
    )
python
# view(配膳する): ここは極限まで薄い
class NextPhotoView(APIView):  # APIView = DRF が用意する view の土台クラス
    def get(self, request):
        photo = pick_next_photo(request.user)
        if not photo:
            return Response({"detail": "no photo"}, status=404)
        return Response(PhotoSerializer(photo).data)  # モデルを JSON に変換して返す

薄い view は、読み返すとき効く

分ける前は「next photo を返す view」を読むだけで、絞り込み条件と並び順と HTTP の作法が全部一度に目に入ってきて、何がアプリのルールで何がただの配線なのか分からなかった。

行は PostgreSQL。応答の JSON はメモリで保存しない。変えたい場所は右の3つに分かれる。分ける前1つの view に全部対象(綴り無し・ACTIVE)優先(スキップ少・古い順)HTTP(404 か JSON)どれがルールか分からない分けたあと変えたい場所が分かれる対象 → selector優先 → serviceHTTP → viewJSON はメモリ。表には書かない

view を薄く保つと、その薄さ自体が「ここにはルールを書かない」という合図になって、後から読む自分が迷わなくなります。


この記事は2026年6月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。