コントローラー・サービス・リポジトリの役割と配置場所をわかりやすく解説!初心者向けSpringアーキテクチャ入門
新人
「Springのプロジェクトでよく聞く『コントローラー』や『サービス』って、具体的に何の役割があるんですか?」
先輩
「それぞれに大事な役割があるよ。コントローラーはリクエストの入り口、サービスは処理の中心、リポジトリはデータのやり取りを担当しているんだ。」
新人
「なるほど……でも、なぜ3つに分ける必要があるんでしょうか?」
先輩
「その理由も含めて、順番に説明していくね!」
1. コントローラー・サービス・リポジトリとは?
Spring Frameworkを使ったJava開発では、処理をわかりやすく整理するために「コントローラー」「サービス」「リポジトリ」という3つの層(レイヤー)に分けるのが一般的です。
コントローラー(Controller)は、ユーザーからのリクエストを最初に受け取るクラスです。たとえば「URLにアクセスされたときにどう反応するか」を決めます。Springでは@Controllerアノテーションを使って定義します。
サービス(Service)は、アプリケーションのビジネスロジックを担う役割です。「ユーザーの登録処理」「商品の価格計算」などの具体的な処理内容をまとめます。
リポジトリ(Repository)は、データベースとやりとりをする役割です。たとえば「データの読み取り」「保存」「削除」などを行います。@Repositoryアノテーションで定義され、内部でJPAやJDBCを使ってデータベースと通信します。
このように役割を明確に分けることで、アプリケーションの構造が整理され、コードの保守や再利用がしやすくなります。
2. なぜこの3つに分けるのか
「コントローラー」「サービス」「リポジトリ」に処理を分ける理由は、役割ごとの責任を明確にして、アプリケーションを効率よく保守・拡張できるようにするためです。
例えば、すべての処理を1つのクラスに書いてしまうと、以下のような問題が発生します。
- コードの行数が増えて読みづらくなる
- 修正箇所が分かりにくくなる
- テストがしにくくなる
それぞれの層が担う責任は以下のとおりです。
- Controller:リクエストを受け取り、必要な処理に振り分ける
- Service:実際のビジネスロジックを実行する
- Repository:データベースとのやり取りを行う
この3層構造にすることで、たとえば「ビジネスロジックだけを変更したい」と思ったときに、サービスクラスだけを修正すればよくなります。他の層に影響を与えずに機能の追加や変更ができるのは、大きなメリットです。
また、ユニットテストを行う際にも、それぞれの層を独立してテストできるため、バグの発見や修正もスムーズになります。
Springではこのような設計を自然にサポートする機能が多数用意されており、PleiadesでGradleプロジェクトを作成し、必要な依存関係(Spring Web、Thymeleafなど)をチェックボックスで追加すれば、すぐにこの構成を実現できます。
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "こんにちは、Springの世界へ!");
return "hello";
}
}
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class HelloService {
public String getGreeting() {
return "サービスからの挨拶メッセージです。";
}
}
package com.example.demo.repository;
import org.springframework.stereotype.Repository;
@Repository
public class HelloRepository {
public String findData() {
return "データベースから取得した情報(仮データ)";
}
}
3. 各役割の具体的な役目と処理の流れ
ここでは、実際に「コントローラー」「サービス」「リポジトリ」がどのように連携しながら動作するのかを、より具体的に説明します。Springアプリケーションでは、この3つのクラスが役割分担をして、1つの処理を効率よく行います。
まず、ControllerはWebブラウザや画面からのリクエスト(要求)を受け取る場所です。ユーザーがURLにアクセスしたとき、そのリクエストを受け取って処理を開始します。しかし、コントローラー自身は複雑な処理は行いません。処理の本体は、Serviceに任せます。
次に、Serviceは、コントローラーから渡された情報をもとに、ビジネスロジックを実行します。たとえば「商品を検索する」「会員登録を行う」「在庫を更新する」などの、実際の業務に関わる処理がこの中に含まれます。
そして、Repositoryは、サービスクラスからの指示に従って、データベースへのアクセス処理を実行します。「データを保存する」「データを検索する」といった、データに関する処理が担当範囲です。
このようにして、処理は以下のような流れで進みます。
- ① ユーザーがブラウザからURLへアクセス
- ② Controllerがリクエストを受け取る
- ③ ControllerはServiceに処理を依頼する
- ④ ServiceはRepositoryを使ってデータベースにアクセス
- ⑤ Repositoryがデータを返し、Serviceが処理をまとめる
- ⑥ 最終的に結果をControllerが受け取り、画面へ返す
このような処理の流れを理解することで、Springアプリケーションがどのように動いているのかをより深く把握できます。
4. 「Controller → Service → Repository」の流れのイメージ図や具体例
ここでは「コントローラー」「サービス」「リポジトリ」の役割分担を、具体的な例で説明します。
たとえば、ユーザーが「こんにちは」という挨拶メッセージを取得したい場合の流れを考えてみましょう。
@Controller
public class GreetController {
private final GreetService greetService;
public GreetController(GreetService greetService) {
this.greetService = greetService;
}
@GetMapping("/greet")
public String greet(Model model) {
String message = greetService.getGreetMessage();
model.addAttribute("message", message);
return "greet";
}
}
@Service
public class GreetService {
private final GreetRepository greetRepository;
public GreetService(GreetRepository greetRepository) {
this.greetRepository = greetRepository;
}
public String getGreetMessage() {
return greetRepository.findGreeting();
}
}
@Repository
public class GreetRepository {
public String findGreeting() {
return "こんにちは、Springユーザー!";
}
}
この例では、/greetというURLにアクセスすると、コントローラーが呼び出され、サービス経由でリポジトリからメッセージが取得されます。そしてそのメッセージが画面に表示されます。
このように「Controller → Service → Repository」の順で処理が流れることにより、それぞれの役割が明確になり、コードの見通しも良くなります。
また、この構成を守ることで、たとえば「データ取得方法を変更したい」「処理ロジックを別に分けたい」というときにも、他の層に影響を与えずに安全に修正できます。
この責務の分離こそが、Spring開発における大きな設計思想の一つです。
初心者のうちは少し複雑に感じるかもしれませんが、実際に使ってみると非常に自然な流れになっていることがわかるはずです。
PleiadesでGradleプロジェクトを作成する場合も、この構成を意識して、パッケージごとにファイルを整理しておくことで、後からの開発や保守がとても楽になります。
5. 各クラスの配置場所(パッケージ構成の基本ルール)
Springアプリケーションを効率よく開発・管理するためには、パッケージ構成が非常に重要です。初心者のうちはついクラスを一箇所にまとめがちですが、役割ごとにパッケージを分けておくことで、後々の保守性が大きく向上します。
まず、Spring BootプロジェクトをPleiadesで作成した際には、com.example.demoのような基本パッケージが生成されます。この直下に以下のようなサブパッケージを作るのが一般的な構成です。
- controller:コントローラークラスを配置
- service:サービスクラスを配置
- repository:リポジトリクラスを配置
- model:エンティティ(データ構造)などを配置
このように、処理の責務ごとにパッケージを明確に分けておくことで、どこに何があるかが一目で分かるようになります。また、開発が進むにつれてクラス数が増えても、役割ごとに整理されているため迷うことが少なくなります。
具体的な構成例を以下に示します。
com.example.demo
├── controller
│ └── HelloController.java
├── service
│ └── HelloService.java
├── repository
│ └── HelloRepository.java
├── model
│ └── Hello.java
このようにフォルダ(パッケージ)で整理することで、責務の分離が明確になり、チーム開発でも混乱を防ぐことができます。
6. よくあるミスと注意点(Controllerでロジックを書かない/Repositoryが肥大化しないように)
Springでアプリケーションを開発する際、初心者がつまずきやすいポイントがあります。特に以下の2点は注意が必要です。
コントローラーは、あくまでリクエストを受け取り、サービスに処理を渡す役割です。しかし、初心者のうちはつい、すべての処理を@Controllerの中に書いてしまいがちです。
たとえば、計算やデータ整形、判定などのビジネスロジックをコントローラーに直接書くと、コードが肥大化して読みにくくなります。責任の分離がされていない状態では、修正や拡張の際に問題が起きやすくなります。
そうした処理は必ず@Serviceに移し、コントローラーは受け渡し専用と考えるようにしましょう。
@Repositoryクラスも注意が必要です。単にデータを取得・保存するだけなら問題ありませんが、場合によっては複雑なクエリ処理や計算処理を含めてしまい、肥大化することがあります。
Repositoryはあくまで「データベースと通信する役割」に限定し、ビジネスロジックやデータの加工はServiceに委ねましょう。
また、メソッドの数が増えすぎた場合は、必要に応じてリポジトリを分割したり、名前を工夫したりして、責務が集中しないように設計することが重要です。
- パッケージが適切に分かれていないと、自動スキャンでクラスが認識されない場合がある
- 依存注入(DI)の書き方が間違っていると、
NullPointerExceptionが出やすくなる - Controllerから直接Repositoryを呼ばないようにする
これらの注意点をおさえることで、より保守性の高い、拡張しやすいSpringアプリケーションが実現できます。
7. 最後に、初心者がこの設計を理解して守るべきポイントの整理
最後に、ここまで学んできた「コントローラー」「サービス」「リポジトリ」の設計において、初心者が必ず意識しておくべきポイントをまとめておきます。
- 役割分担をしっかり意識すること
コントローラーは受け口、サービスは処理の中枢、リポジトリはデータとのやりとり。これを常に意識してクラスを設計しましょう。 - パッケージ構成を正しく分ける
各層を明確にパッケージ分けすることで、可読性と保守性が向上します。 - 複雑な処理をコントローラーやリポジトリに書かない
ビジネスロジックの中心はサービスに置くのが基本です。 - ファイルの配置場所に注意する
Pleiadesで作成したプロジェクトでは、mainディレクトリ配下に整理しておくことで、Springの自動構成が正しく働きます。
これらの基本を守ることで、Springを使ったJavaアプリケーションの設計がしっかりとしたものになり、保守性の高い構造を実現できます。
初心者のうちはとにかく「役割を分けること」「一箇所にまとめすぎないこと」を意識して、シンプルで読みやすいコードを心がけましょう。