【Spring Boot入門】設計の基本を完全解説!「動くコード」から「良いコード」へ
Spring Bootの設計で悩んでいませんか?本記事では、新人エンジニア向けにレイヤードアーキテクチャやパッケージ構成など、良い設計の基本を徹底解説。動くコードから保守性の高いコードへステップアップしましょう。
キャリアパス診断してみるはじめに:Spring Bootで「とりあえず動く」コードから卒業したいあなたへ
「Spring Bootのチュートリアルを終えて、簡単なAPIは作れるようになった。でも、実務でコードを書き始めると、どこに何を書けばいいのか分からなくなる…」
「先輩のコードレビューで『このロジックはService層に書くべき』と指摘されたけど、なぜそうなのか自信を持って説明できない…」
もしあなたが今、このように感じているなら、この記事はまさにあなたのためのものです。
Javaフレームワークの代表格であるSpring Bootは、非常に強力で手軽にWebアプリケーションを開発できます。しかしその手軽さゆえに、「とりあえず動く」コードは書けても、「良い設計」のコードを書くという次のステップで多くの入門者が壁にぶつかります。
この記事では、新人エンジニアのあなたが「動くコード」から「保守性が高く、拡張しやすい良いコード」を書けるようになるための、Spring Bootにおける設計の基本を徹底的に解説します。
多くの入門記事が単なる役割説明で終わるところを、本記事では「なぜそう設計するのか?」という根本的な理由と、新人が陥りがちなNG例を交えながら、明日から実践できる知識を提供します。
この記事を読み終える頃には、あなたは自信を持ってコードの役割分担を決め、チームメンバーからも信頼される設計ができるようになっているはずです。
なぜSpring Boot開発で「設計」が重要なのか?
そもそも、なぜ私たちは「設計」を意識する必要があるのでしょうか?動けば良いのでは?と思うかもしれません。しかし、特にチームで長期間開発するプロダクトにおいて、良い設計は未来の生産性を大きく左右します。
未来の自分と同僚を救う「保守性」
良い設計のコードは、誰が読んでも理解しやすく、修正が容易です。半年後に自分が書いたコードを見て「何だこれ…?」と頭を抱えたり、他のメンバーがあなたのコードを修正するのに何時間も費やす、といった事態を防ぎます。
仕様変更に強くなる「拡張性」
「ここに新しい機能を追加してほしい」といった急な仕様変更は日常茶飯事です。良い設計は、各機能が適切に分離されているため、変更の影響範囲を最小限に抑え、安全かつ迅速に機能追加を行えます。
チーム開発をスムーズにする「再利用性」
一度書いたコードを別の場所でも使い回せるように部品化しておくことで、開発効率は劇的に向上します。良い設計は、コードの再利用性を高め、チーム全体の生産性を底上げします。
これだけは押さえたい!Spring Boot設計の基本「レイヤードアーキテクチャ」
では、具体的にどうすれば「良い設計」ができるのでしょうか。その第一歩が「レイヤードアーキテクチャ」を理解することです。これは、Spring Bootにおける最も基本的で重要な設計パターンです。
レイヤードアーキテクチャとは?3つの層の役割分担
レイヤードアーキテクチャは、アプリケーションの機能を役割ごとに層(レイヤー)に分ける設計思想です。これにより、各層が自分の仕事に集中でき、コードの見通しが格段に良くなります。
Spring Bootでは、主に以下の3つの層に分けられます。
- Controller層(Presentation Layer)
- Service層(Business Logic Layer)
- Repository層(Data Access Layer)
それぞれの役割を、レストランの仕事に例えて見ていきましょう。
① Controller層:リクエストを受け取る「受付係」
Controller層は、外部からのリクエスト(HTTPリクエスト)を最初に受け取る窓口です。レストランで言えば、お客さんの注文を受けるホールスタッフ(受付係)の役割です。
主な仕事:
- 特定のエンドポイント(URL)へのリクエストを受け付ける。
- リクエストに含まれるデータ(JSONなど)を受け取り、後続のService層が扱いやすい形に変換する。
- Service層の処理結果を受け取り、レスポンス(JSONなど)としてクライアントに返す。
注意点: Controller層にはビジネスロジック(計算や複雑な条件分岐など)を書くべきではありません。 あくまでリクエストとレスポンスの橋渡しに徹します。
// UserController.java
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
// ... コンストラクタ
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {
// Service層に処理を依頼するだけ
UserData userData = userService.findById(id);
// 結果をレスポンス用の形式に変換して返す
return new UserResponse(userData.getName(), userData.getEmail());
}
}② Service層:ビジネスロジックを実行する「司令塔」
Service層は、アプリケーションの核となるビジネスロジックを担当します。レストランで言えば、注文に応じて調理方法を判断し、各担当に指示を出すシェフ(司令塔)です。
主な仕事:
- Controllerから依頼された処理を実行するための、具体的な手順(ビジネスロジック)を実装する。
- 必要に応じて、複数のRepositoryを組み合わせて複雑な処理を行う。
- トランザクション管理(複数のデータベース操作をまとめる処理)などを行う。
// UserService.java
@Service
public class UserService {
private final UserRepository userRepository;
private final PointRepository pointRepository; // 別のRepositoryも利用する
// ... コンストラクタ
public UserData findById(Long id) {
// Repositoryを使ってデータを取得
User user = userRepository.findById(id).orElseThrow(() -> new UserNotFoundException());
// ビジネスロジック(例:ポイント情報を付与する)
Point point = pointRepository.findByUserId(id);
return new UserData(user, point);
}
}③ Repository層:データベースと対話する「専門家」
Repository層は、データベースとのやり取り(データの保存、取得、更新、削除)のみを担当します。レストランで言えば、特定の食材を冷蔵庫から取ってくる専門のスタッフです。
主な仕事:
- データベースへのアクセス(CRUD操作)を実装する。
- JPA (Java Persistence API) を利用する場合、JpaRepositoryインターフェースを継承することが多い。
注意点: Repository層は、データベース操作以外のロジックを持つべきではありません。 データの取得方法を知っているだけで、そのデータをどう使うかは知りません。
// UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Spring Data JPAがメソッド名から自動でSQLを生成してくれる
Optional<User> findByEmail(String email);
}このように役割を明確に分けることで、「データの取得方法を変えたい」場合はRepository層だけを修正すればよく、「ユーザー登録時のルールを変えたい」場合はService層を修正すればよい、というように変更の影響範囲を限定できます。
実践!よくあるNG例と見違えるパッケージ構成
理論は分かっても、実際のファイル構成はどうすれば良いのでしょうか。ここで新人が陥りがちなNG例と、推奨されるパッケージ構成を見てみましょう。
【ありがちNG例】Serviceクラスに全てを詰め込んでしまう…
最初は機能が少ないため、UserServiceにユーザー関連のロジックを全て書いてしまいがちです。しかし、機能が増えるにつれてUserServiceは数千行にも及ぶ巨大なクラスになり、誰も触りたくない「神クラス」が誕生してしまいます。
【推奨例】責務で分けるパッケージ構成でコードを整理しよう
基本的なパッケージ構成は、レイヤーごと、あるいは機能(ドメイン)ごとにまとめる方法が一般的です。
レイヤーで分ける構成:
com.example.app
├── controller
│ └── UserController.java
├── service
│ └── UserService.java
├── repository
│ └── UserRepository.java
├── entity
│ └── User.java
└── dto
└── UserResponse.java小規模なアプリケーションではこの構成がシンプルで分かりやすいです。
機能(ドメイン)で分ける構成:
com.example.app
├── user // ユーザー関連
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ ├── User.java
│ └── UserResponse.java
├── product // 商品関連
│ ├── ProductController.java
│ ├── ProductService.java
│ └── ...
└── order // 注文関連
├── OrderController.java
├── OrderService.java
└── ...大規模なアプリケーションになるほど、機能ごとにまとまっているこちらの構成の方が、関連するファイルを見つけやすく、管理しやすくなります。
「良い設計」を支えるSpring Bootの重要機能
レイヤードアーキテクチャを支えているSpringの重要な仕組みについても触れておきましょう。
DI(依存性の注入)がコードを綺麗にする仕組み
先ほどのコード例で、UserServiceはUserRepositoryを利用していましたが、UserServiceの中でnew UserRepository()のようにインスタンスを生成していませんでした。これはDI(Dependency Injection / 依存性の注入)というSpringの機能によるものです。
DIとは、クラスが必要とするオブジェクト(依存関係)を、自分自身で生成するのではなく、外部から与えてもらう(注入してもらう)仕組みです。これにより、各クラスは疎結合(お互いの依存度が低い状態)になり、テストがしやすくなったり、部品の差し替えが容易になるという大きなメリットがあります。
責務分離の考え方と「単一責任の原則」
ここまで解説してきたことは、ソフトウェア設計の「単一責任の原則(Single Responsibility Principle)」という考え方に基づいています。これは、「一つのクラスは、一つの責任だけを持つべき」という原則です。
- Controllerは「リクエストとレスポンス」に責任を持つ。
- Serviceは「ビジネスロジック」に責任を持つ。
- Repositoryは「データアクセス」に責任を持つ。
このように責任を分離することで、変更に強い柔軟なシステムを構築できるのです。
設計スキルを身につけて、市場価値の高いエンジニアを目指そう
良い設計スキルは、単に綺麗なコードが書けるようになるだけではありません。それは、問題解決能力やシステム全体を俯瞰する力があることの証明です。
このスキルは、あなたのエンジニアとしての市場価値を大きく高めます。より複雑で大規模なプロジェクトに挑戦するチャンスが増え、キャリアアップにも直結します。
もしあなたが自身のスキルアップやキャリアについてさらに深く考えたいなら、専門のエージェントに相談してみるのも一つの手です。
まとめ:良い設計は、あなたを一流のエンジニアに育てる
今回は、Spring Bootにおける設計の基本として、以下の点を解説しました。
- なぜ設計が重要か: 保守性、拡張性、再利用性を高めるため。
- レイヤードアーキテクチャ: Controller, Service, Repositoryの3層で役割を分ける。
- 各層の責務: Controllerは受付係、Serviceは司令塔、Repositoryは専門家。
- 良い設計の原則: DIや単一責任の原則が良いコードを支えている。
今日学んだことを意識してコードを書くだけで、あなたのコードは間違いなく変わります。最初は難しく感じるかもしれませんが、繰り返し実践するうちに、自然と良い設計が身についていくはずです。
「動くコード」の先に広がる「良い設計」の世界へ、今日から一歩踏み出しましょう。

応エン