綴Photo のバックエンドを Django(Pythonのバックエンド用フレームワーク)と DRF(Django REST Framework。APIを作る追加部品)で組むときに、selector / service / view を分けた話。勉強のため。
view に「DB からどう取るか」「どれを選ぶか」「どう返すか」を全部書くと、一つの関数が何でも知っている状態になります。そこを3つの係に分けたら、view が驚くほど薄くなった。
3つの係に分ける
頭の中ではこう置いている。selector が材料を集め、service が料理し、view が配膳します。
「次に意味づけする写真を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 の作法が全部一度に目に入ってきて、何がアプリのルールで何がただの配線なのか分からなかった。
view を薄く保つと、その薄さ自体が「ここにはルールを書かない」という合図になって、後から読む自分が迷わなくなります。
この記事は2026年6月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。