責務を分けない場合に起きやすい問題
ControllerとServiceの責務を分けずに実装すると、Controllerが業務処理を持ち始めると、責務が混ざりやすくなります。。
例えば、以下のようなコードです。
@PostMapping("/signup")
public ResponseEntity<Void> signup(
@RequestBody SignupRequest request
) {
if (accountRepository.existsByEmail(request.email())) {
throw new DuplicateEmailException();
}
Account account = new Account(
request.email(),
passwordEncoder.encode(request.password())
);
accountRepository.save(account);
return ResponseEntity.ok().build();
}
このようなコードになると、
- HTTP処理
- DB確認
- 業務ルール
- データ保存
がすべてControllerに集まってしまいます。
すると、以下のような問題が起きやすくなります。
- Controllerが肥大化する
- 処理の再利用がしづらくなる
- テストしづらくなる
- 業務処理の場所がバラつく
特に、同じ業務処理を別APIから使いたくなった場合、Controllerに直接書いていると再利用しづらくなります。
また、処理が増えていくと「どこに何を書くべきか」がさらに曖昧になり、責務分離が崩れやすくなります。
Controllerで扱う処理
Controllerは「Webとの入り口」を担当するイメージです
- リクエストの受け取り
- PathVariable や QueryParam の取得
- リクエストボディの受け取り
- Bean Validation(
@Valid) - HTTPステータスの返却
- Serviceへ処理を委譲
例
@PostMapping("/signup")
public ResponseEntity<Void> signup(
@Valid @RequestBody SignupRequest request
) {
authService.signup(request);
return ResponseEntity.ok().build();
}
この例では、Controllerは以下のみを担当しています。
- リクエストを受け取る
- バリデーションを行う
- Serviceへ処理を委譲する
- レスポンスを返す
Serviceで扱う処理
Serviceでは、業務ルールやアプリケーションの処理を扱います。
- DBを使った存在確認
- 重複チェック
- 権限判定
- 状態変更
- 複数Repositoryを使った処理
- トランザクション管理
特に、「Repositoryを使い始める処理」はServiceに寄せることが多いです。
例えば、メールアドレスの重複チェックは単なる入力チェックではなく、DBの状態を使った業務ルールになります。
public void signup(SignupRequest request) {
if (accountRepository.existsByEmail(request.email())) {
throw new DuplicateEmailException();
}
Account account = new Account(
request.email(),
passwordEncoder.encode(request.password())
);
accountRepository.save(account);
}
この例では、
- メールアドレス重複確認
- パスワードエンコード
- アカウント保存
といった業務処理をServiceでまとめています。
まとめ
ControllerとServiceの責務を分けることで、HTTP処理と業務処理の役割を整理しやすくなります。
厳密な正解があるわけではありませんが、「HTTPに近い処理か、業務ルールか」を基準にすると判断しやすくなります。