AIによる要約
入力チェックは、すべてControllerでif文を書くものではありません。必須、桁数、形式のようなチェックはFormやRequest DTOのBean Validationに寄せ、重複確認、権限、状態遷移のような業務ルールはServiceで判断する、と分けると現場コードが読みやすくなります。


Spring Bootで登録画面やAPIを作ると、入力チェックをどこに書くかで迷います。
Controllerで全部if文を書く、Formのアノテーションだけに任せる、Serviceでまとめて見るなど、複数の選択肢があります。現場では、どのチェックをどこに置くかがレビュー対象になります。
この記事のポイント
- 必須、桁数、形式はBean Validationに寄せる
- 重複、権限、状態などの業務チェックはServiceで見る
- Controllerは入力エラー時の画面/API応答を制御する
- チェックの場所を分けるとレビューで説明しやすい
入力チェックには種類がある
入力チェックと一言で言っても、実際には種類があります。必須、桁数、形式のようなチェックと、業務ルールに関するチェックは分けて考えます。
- 形式チェック: 必須、最大文字数、メール形式、数値範囲
- 業務チェック: メールアドレスの重複、注文状態、権限、締切日
この2つを同じ場所に全部書くと、ControllerやServiceが読みづらくなります。
形式チェックはFormやRequest DTOに寄せる
必須、桁数、形式のようなチェックは、Bean ValidationでFormやRequest DTOに書くのが分かりやすいです。
public class UserRegisterForm {
@NotBlank
@Size(max = 50)
private String name;
@NotBlank
@Email
private String email;
}
このようなチェックは、業務ロジックというより入力の形の問題です。Formに書くことで、Controllerではエラーの有無を見て画面に戻せます。
Controllerはエラー時の戻し先を決める
Controllerの役割は、入力値を受け取り、形式チェックの結果を見て、次に進むか画面へ戻すかを決めることです。
@PostMapping("/users")
public String register(@Valid UserRegisterForm form,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "users/new";
}
userService.register(form.toCommand());
return "redirect:/users";
}
ここで重複確認や権限判定までControllerに書き始めると、Controllerが業務ロジックの置き場になってしまいます。
業務チェックはServiceで行う
メールアドレスが既に使われているか、注文をキャンセルできる状態か、ログインユーザーに権限があるか。こうしたチェックは業務ルールなのでServiceに置くのが自然です。
@Service
public class UserService {
public void register(UserRegisterCommand command) {
if (userRepository.existsByEmail(command.getEmail())) {
throw new DuplicateEmailException(command.getEmail());
}
User user = new User(command.getName(), command.getEmail());
userRepository.save(user);
}
}
このチェックは、画面から登録してもAPIから登録しても必要です。だからControllerではなくServiceに置く方が再利用しやすくなります。
Controllerに業務チェックを書くと何が困るか
@PostMapping("/users")
public String register(@Valid UserRegisterForm form,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "users/new";
}
if (userRepository.existsByEmail(form.getEmail())) {
bindingResult.rejectValue("email", "duplicate", "既に使われています");
return "users/new";
}
userService.register(form);
return "redirect:/users";
}
このコードは一見親切ですが、ControllerがRepositoryを直接見て業務ルールを判断しています。API版の登録処理を作ったとき、同じ重複チェックをもう一度書くことになります。
Serviceの例外を画面でどう扱うか
Serviceで業務チェックを行う場合、Controller側で例外を受けて画面に戻す設計もあります。プロジェクトによっては、専用の例外ハンドラでまとめることもあります。
try {
userService.register(form.toCommand());
} catch (DuplicateEmailException e) {
bindingResult.rejectValue("email", "duplicate", "既に使われています");
return "users/new";
}
return "redirect:/users";
ここで大事なのは、重複かどうかを判断するのはService、画面にどう表示するかを決めるのはController、という分担です。
現場レビューでの見られ方
// レビューコメント例 メール重複チェックは業務ルールなのでService側に寄せたいです。 // レビューコメント例 ControllerでRepositoryを直接呼んでいるため、責務が混ざっています。 Serviceにメソッドを用意してControllerは結果の戻し先を決める形にしましょう。
レビュー前のセルフチェック
確認リスト
- 必須・桁数・形式チェックをFormやRequest DTOに書けているか
- 重複・権限・状態チェックをControllerに書きすぎていないか
- Serviceに置くべき業務ルールをControllerで判断していないか
- 画面とAPIで同じ業務チェックを重複実装していないか
この記事とあわせて読みたい
- BindingResultの使い方と注意点|@Validの直後に置く理由
- Spring Bootの@Validと@Validatedの違いは?入力チェックの使い分け
- Spring BootのServiceに業務ロジックを書く理由
まとめ
入力チェックは、Controllerに全部書けばよいものではありません。形式チェックはFormやRequest DTOのBean Validationへ、業務チェックはServiceへ置くと責務が整理されます。
新人のうちは、チェック内容を見たときに「入力の形の問題か」「業務ルールの問題か」を分けるだけで、Spring Bootの実装判断がかなり安定します。
