11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

既存のプロジェクトにアサインされて、自分の最初のタスクはバグ修正でした。

コードを開いてみると、こんなフォルダ構成でした。

app/
├── controllers/
│   └── cart_controller.py
├── services/
│   └── cart_service.py
├── repositories/
│   └── cart_repository.py
└── models/
    └── cart_item.py

なんとなく意味は分かるけど、どこに何を書くべきかは分かりません。とりあえず動けばいいかと、Controllerにロジックをどんどん書きました。

そしたら、レビューでこんな指摘が入りました。

スクリーンショット 2026-03-31 23.19.06.png

Serviceへの移譲...。聞いたことはあったけど、ちゃんと理解していませんでした。

そこから構造を調べ直して、コードを直しました。

レイヤードアーキテクチャとは

レイヤードアーキテクチャは、役割ごとにコードを層(レイヤー)に分ける構造です。それぞれの層が「自分の仕事だけ」をやります。

スクリーンショット 2026-03-31 22.37.21.png

ほとんどのレイヤードアーキテクチャは、プレゼンテーション層、ビジネス層、永続化層、データベース層の4つの標準的なレイヤーで構成されている。

——『ソフトウェアアーキテクチャの基礎』10章

登場人物の役割

まず、このプロジェクトで登場するクラスの役割を整理します。

役割 一言でいうと
Controller 受付・振り分け
Request 入力チェック
Service 業務ロジック
Entity DBの1レコード
ValueObject ルール・計算
Repository DBの読み書き

処理の流れ(全体像)

ユーザーの操作は、以下のように流れていきます。

  1. Controller がリクエストを受け取る
  2. Service に処理を依頼する
  3. Entity / ValueObject で状態やルールを扱う
  4. Repository を通してデータを保存する

スクリーンショット 2026-03-31 22.44.04.png

ポイントは、それぞれが「隣の人の仕事を知らない」ということです。

Controllerさんは、Requestの中身をチェックして、Serviceさんに「これお願い」と渡すだけです。
Serviceさんがどうやって処理しているかは知りません。

Serviceさんは、業務の流れを組み立てますが、データがどこに保存されるかは知りません。
「保存して」とRepositoryさんに頼むだけです。

Repositoryさんは、DBとやりとりしますが、なぜその保存が必要なのかは知りません。
言われた通りに保存するだけです。

この「知らない」が大事です。
知らないからこそ、どこかを変えても他に影響が出にくくなります。

カート追加の処理を追ってみる

コードは責務の理解を優先してシンプルにしています。実務では認可・トランザクション管理・バリデーションなどが加わります。

Controllerは「受け取って渡す」

初に自分がやらかしたのがここです。Controllerに在庫チェックや価格計算を全部書いてしまいました。

Controllerの本来の役割はシンプルです。

  • リクエストを受け取る
  • 適切な処理に渡す

それだけです。業務ロジックは持ちません。

from fastapi import APIRouter
from pydantic import BaseModel

router = APIRouter()

class AddToCartRequest(BaseModel):
    product_id: int
    quantity: int

@router.post("/cart")
def add_to_cart(req: AddToCartRequest):
    return cart_service.add(req.product_id, req.quantity)

Serviceは「業務の流れを組み立てる」

Controllerから渡された処理を、Serviceが組み立てます。

  • 在庫確認
  • 価格取得
  • カートへの追加
class CartService:
    def add(self, product_id: int, quantity: int):
        product = product_repository.find(product_id)
        cart_item = CartItem(product, quantity)  # Entity を生成
        cart_repository.save(cart_item)

Entityは「状態を持つ」

Entityはデータと状態の整合性を持つオブジェクトです。

class CartItem:
    def __init__(self, product, quantity):
        if quantity <= 0:
            raise ValueError("数量は1以上である必要があります")
        self.product = product
        self.quantity = quantity

    def total_price(self):
        return self.product.price * self.quantity

ValueObjectは「ルールを持つ」

価格計算のような、明確なルールを持つものは分離します。

class Price:
    def __init__(self, base_price: int):
        self.base_price = base_price

    def with_tax(self):
        return int(self.base_price * 1.1)

Repositoryは「保存と取得をする」

Repositoryはデータアクセスを担当します。

class CartRepository:
    def __init__(self, db: Session):
        self.db = db

    def save(self, cart_item: CartItem):
        self.db.add(cart_item)
        self.db.commit()

なぜ分けるのか

レビューで指摘されたとき、正直「そんなに大事?」と思っていました。

でも直してみて分かりました。Controllerを開いたとき、「受け取って渡すだけ」のコードしかないので、処理の流れが追いやすい。

逆にここにロジックを書き始めると、一気に見通しが悪くなります。

DB構造を変えたいときはRepositoryだけ見ればいい。

どこに何があるかが分かるから、修正も迷わなくなりました。

おわりに

既存のプロジェクトにアサインされて、自分の最初のタスクはバグ修正でした。

あのとき cart_controller.py を開いて、どこに何を書けばいいか分からなかった自分が、CodeRabbitの指摘をきっかけに構造を調べ直して、コードを直しました。

知らないアーキテクチャのプロジェクトにアサインされて困っている人の参考になれば。

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?