0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Controller・Service・Repositoryの役割

Last updated at Posted at 2025-10-04

はじめに

バックエンドを実装するかもしれないので、改めて、Controller・Service・Repositoryの役割を整理してみました。

Controller・Service・Repositoryの役割について

Controller

役割

ユーザーからのHTTPリクエストを最初に受け取り、その結果をレスポンスとして返す「入り口」の役割を担う。
JSONとDTOの相互変換やリクエスト内容のバリデーションを行い、実際の処理はService層に委譲。

注意点

重要なのは、ビジネスロジックをControllerに直接書かないこと。
Controllerはあくまで入り口に徹し、処理の流れを整えることに専念するようにする。

Service

役割

システムにおけるビジネスロジック*の中心。
複数のRepositoryを組み合わせて業務ルールに沿った処理を実装したり、必要に応じてトランザクションを管理したりする。
業務上の振る舞いを整理することで、アプリケーション全体の整合性を保つ。

※ビジネスロジックとは
業務に特有のルールや計算・処理の流れを実装した部分のこと。
(例)

  • 契約期間が満了したら自動更新する
  • 月額プランと年額プランで請求金額を計算する
  • 支店数を超えたら追加課金する など

注意点

全てをServiceに詰め込むとFat Serviceと呼ばれる過剰に肥大状況*が発生することがある。
ドメインモデルとの役割分担を意識し、適切に責務を分けることが重要になる。

※Fat Serviceとは
Service層に本来分けるべき責務を全部詰め込んでしまって、過剰に肥大化した状態。

さらに深掘りFat Service

【Fat Serviceが生まれる状況】
Fat Serviceが生まれるは、下記のようなとき。

  • 本来はドメインモデルやRepositoryに委譲すべき処理*までServiceに書いてしまう
  • Controllerから呼ばれる唯一の場所だからとにかく「ここに置けば動く」発想で全部寄せる
  • メソッド数がどんどん増えていき、1クラスで数千行にも膨れ上がる

※ドメインモデル
ビジネスルールや振る舞い。
そのモデルが自分自身を正しく保つためのロジック。
エンティティや値オブジェクトに閉じたルール。
(例)ユーザーのパスワード更新ロジックや、注文の「合計金額を計算する」「キャンセル可能かを判定する」

  • エンティティ
     IDを持ち、ライフサイクルにわたって同一性を保つクラス
    「この契約」「この店舗」「このユーザー」と特定できる存在
  • 値オブジェクト
     値そのものが意味を持ち、不変で使い回せるクラス
     住所、金額など値が同じなら同じものと扱えるもの
  • ドメインサービス
     ビジネスルールとして必要な振る舞い

ドメインモデル = エンティティや値オブジェクト(+必要に応じてドメインサービス)で表現されたビジネスルールの集合!
→ サービスに直接は書かない!サービスはそれを呼び出すだけ!

※Repositoryに移譲すべき処理

  • データ取得/永続化に関する処理
  • ドメインに必要なクエリ(ただしロジックは含めない)
    • 「どのデータを持ってくるか」という条件はRepository
    • 「持ってきたデータをどう判断・計算するか」はドメインモデル or Service

「データの取り出し・保存」はRepositoryの責務。ビジネス判断は持ち込まない!

Repository

役割

データベースや外部APIとのやり取りを行う。
SQLやクエリを一元的に管理し、データの取得や保存といったCRUD処理をアプリケーションコードから詳細を意識させない役割を担う。
返却するデータはドメインモデルにマッピングされることが多く、ビジネスロジックを持ち込まないことが設計上の原則。
Repositoryはあくまで「データアクセスの窓口」と割り切り、責務を限定することで、全体の構造を明確に保つことができる。

もし役割を意識しなかったら?

Controllerにビジネスロジックを入れると?

画面やAPI仕様変更=Controller変更になることがある*ため、中に業務ロジックまであると毎回ロジックも巻き込まれ、料金計算・状態遷移の修正までセットで発生することになることも。

※画面やAPI仕様変更=Controller変更になることとは
UIやAPIの仕様が変わった場合、場合によって、リクエストDTOやレスポンスDTOの変換をする必要がある。

Serviceがなんでも屋になってしまうと?

メソッドが肥大し、分岐だらけで読めない・直せない状況に。
トランザクション境界が曖昧になり、部分更新や二重実行で不整合(在庫の多重引当など)が発生する可能性も。(あっちこっちで更新が走る、トランザクションがまとまっていない状況)

Repositoryに業務ルールを書いてしまうと?

そのRepositoryを経由しない処理(バッチや別APIなど)でルールが適用されず、
経路ごとに挙動が変わり、不整合や予期せぬエラーが発生する。

役割をさっと確認

下記をチェックすることによって、役割分担がちゃんとできているか確認できそう。

  • Controllerにif/forで業務ルールを書いていないか?
  • @TransactionalはServiceにだけ付いているか?
  • Serviceメソッドは1ユースケース1メソッドになっているか
  • Repositoryメソッド名に業務語が出ていないか?
  • 同じバリデーションを複数層に重複定義していないか?

ちょっと気になったこと

ドメインモデルとDTOの違い

ドメインモデルは業務ルールや不変条件*を表現し、メソッドを通じて正しい振る舞いを保証する内部のオブジェクト。
一方、DTOは入出力専用のデータ構造で、リクエストやレスポンスの形式に合わせて定義され、ビジネスルールは持たない。

※不変条件とは
DTOはあくまで「入出力専用のデータ構造」ですが、アノテーションで@NotNull@Sizeなどのバリデーションを付けるのは一般的にアリです。やシステムの状態が常に満たすべきルールや制約。

DTOにはバリデーションをつけるが、、、?

DTOはあくまで「入出力専用のデータ構造」だが、アノテーションで@NotNull@Sizeなどのバリデーションを付ける。
これは、外部から受け取るデータの形式や必須性を保証するためのもの。
ドメインモデルに含める不変条件はダメで、例えば、在庫は0未満にならない」「契約終了日は開始日以降であるなどは、ドメインモデルで絶対に崩れないよう担保する必要がある。

まとめ

お作法のようにかき分けてきましたが、ここを理解していれば、なんとか責務を分けて、迷わずコードが書けそうな感じがしてきました。
実際にコードを書いてみて、迷った点は追記か別の記事にできる良さそうと思いました。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?