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?

[Spring Boot] Controller層の設計と責務分離

Last updated at Posted at 2025-05-12

はじめに

Spring Bootでの長期インターンで得た経験をもとに、Controller層の設計と分離に関するTipsを紹介します。スタッフを管理するようなシステムを例に、「可読性・再利用性・保守性」を意識したコーディングと、実務で用いられていた典型的な工夫を紹介します(あくまでインターン生として感じた工夫です)。

自己紹介

  • 商学部出身
  • 兵庫の情報系大学院生(27卒予定)
  • サーバーサイド大好き人間
  • 趣味はハッカソン
  • バックエンドエンジニア志望
  • 長期インターンにてSpring Bootを使用(半年以上)

※初記事になるため、温かい目で読んでいただけると幸いです。

背景と目的

多くのチュートリアルや入門書では、Controller層にビジネスロジックを直書きしてしまいがちですが、実務ではそれではスケーラビリティに欠け、保守が困難になります。本記事では、長期インターンで学んだ設計観点として、以下を紹介します:

  • ロジックの粒度分割
  • DTOの導入
  • リダイレクトやバリデーションの設計指針

ロジックの粒度分割:Controllerは「薄く」

Controller層では、受け取り・委譲・返却の3点に集中させ、ロジックはServiceに任せます。こうしたサービス層との責務分離は、単一責任の原則(SRP)の観点から重要です。

@GetMapping("")
public String list(Model model) {
    // Serviceからスタッフ一覧を取得
    List<StaffDto> staffs = staffService.findAll();

    // Viewに渡すため、Modelにデータを格納
    model.addAttribute("staffs", staffs);

    // staff/list.html を表示
    return "staff/list";
}

ControllerはService呼び出しの「仲介役」に徹し、ロジックは書かない。

DTOの導入:出力形式の明示と安定化

EntityをViewに直接渡すのは避け、DTOに変換して表示用に使います。

public class StaffDto {
    private String name;
    private String email;
}

Entityの構造に依存しないことで、将来的な拡張・変更に強くなる。

※ DTO / Form / Entity の役割と違い

種類 役割 使用場所 備考
Entity DB構造 Repository層 JPAと密結合
DTO 出力表示用 Controller → View セキュリティ・公開制御にも有効
Form 入力バインド・検証 View → Controller バリデーション担当

NGパターン:DAOをControllerで直接呼び出す

@GetMapping("")
public String list(Model model) {
    List<Staff> staffs = staffRepository.findAll(); // NG
    model.addAttribute("staffs", staffs);
    return "staff/list";
}

ViewにEntityを直渡しすると、仕様変更に弱く、セキュリティリスクも高い。

リダイレクトとバリデーションの設計指針

リダイレクトでは、Flash Attributeで一度限りのメッセージを表示できます。

@PostMapping("/create")
public String create(@ModelAttribute StaffCreateForm form, RedirectAttributes redirectAttributes) {
    // フォームデータをService層に渡して新規登録処理
    staffService.create(form);

    // Flash Attributeを利用して、一度限りの成功メッセージをリダイレクト先に渡す
    redirectAttributes.addFlashAttribute("successMessage", "登録完了");

    // 一覧画面へリダイレクト(PRGパターン:Post-Redirect-Get)
    return "redirect:/staffs";
}

RedirectAttributes.addFlashAttribute()addAttribute() のようにクエリパラメータにはならず、よりセキュアに情報を渡せる。

また、バリデーションについて、Springでは @ValidBindingResult を使ってバリデーション制御ができます。

public class StaffCreateForm {
    @NotBlank  // 空文字・nullを拒否
    private String name;

    @Email     // メールアドレス形式をチェック
    private String email;
}

@PostMapping("/create")
public String create(@Valid @ModelAttribute StaffCreateForm form,
                     BindingResult result,
                     RedirectAttributes redirectAttributes) {
    // 入力エラーがある場合、フォーム画面を再表示
    if (result.hasErrors()) return "staff/form";

    // バリデーションOKなら、Service経由で登録処理
    staffService.create(form);

    // Flashメッセージをセットし一覧画面へリダイレクト
    redirectAttributes.addFlashAttribute("successMessage", "登録完了");
    return "redirect:/staffs";
}

バリデーションに失敗したら同じ画面を返し、成功時はリダイレクトする構成でUXと安全性を両立する。

※ テスト性向上の実践例(MockMvc)

Controller単体テスト

@WebMvcTest(StaffController.class)
class StaffControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private StaffService staffService;

    @Test
    void testList() throws Exception {
        // Serviceの戻り値をMockで定義
        when(staffService.findAll()).thenReturn(List.of(new StaffDto("山田", "yamada@example.com")));

        // GET /staffs にアクセスし、ビューやモデルの検証を行う
        mockMvc.perform(get("/staffs"))
            .andExpect(status().isOk())
            .andExpect(view().name("staff/list"))
            .andExpect(model().attributeExists("staffs"));
    }
}

@WebMvcTest はController層だけをテスト対象にし、他の依存は @MockBean で注入してテストできる。

まとめ

  • Controllerは「薄く」保ち、Service層にロジックを集約
  • DTO / Form / Entityの役割を明確に
  • バリデーションやリダイレクトを設計に組み込む
  • + テスト性も見越したシンプルな構成を意識する

おわりに

今後も、実務で得た細かい知見をTipsとして紹介していきます。質問や改善のフィードバックがあれば、ぜひコメントで教えてください!

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?