こんにちは、かつコーチです。
ここまでレイヤードアーキテクチャや例外ハンドリングの一元化など、個別のトピックを扱ってきましたが、今回はそれらを踏まえた上で「新しいSpring Bootプロジェクトをどう構成すべきか」を一段俯瞰して整理します。
設計の細部を正しくしても、プロジェクト全体の構成方針がバラついていると、チーム開発では逆にコストが増えます。
この記事では、パッケージ構成・設定管理・共通処理の配置という3つの観点から、実務でよく採用されるベストプラクティスをまとめます。
パッケージ構成の設計方針
レイヤーごと構成と機能ごと構成の比較
Spring Bootのパッケージ構成には、大きく2つの流派があります。
| 方式 | 構成イメージ | 向いている規模 |
|---|---|---|
| レイヤーごと(Layer First) | controller/ service/ repository/でパッケージを分ける | 小〜中規模、機能数が少ないプロジェクト |
| 機能ごと(Feature First / パッケージ・バイ・機能) | order/ user/のように業務機能ごとにControllerからRepositoryまでまとめる | 中〜大規模、機能が増え続けるプロジェクト |
# レイヤーごと構成
com.example.blog
├── controller/ (OrderController, UserController, ...)
├── service/ (OrderService, UserService, ...)
└── repository/ (OrderRepository, UserRepository, ...)
# 機能ごと構成
com.example.blog
├── order/ (OrderController, OrderService, OrderRepository, ...)
└── user/ (UserController, UserService, UserRepository, ...)
レイヤーごと構成は入門時には分かりやすい一方、機能が増えるほど1つのパッケージに無関係なクラスが並び、「注文機能に関するクラスがどこにあるか」を探すコストが上がっていきます。
筆者が15画面規模のプロジェクトをレイヤーごと構成で開発したとき、serviceパッケージだけで30ファイルを超え、目的のクラスを探すのに毎回ファイル名検索を使う羽目になりました。
機能ごと構成に切り替えてからは、1つの業務機能の変更が1つのパッケージ内で完結するようになり、影響範囲の把握が明確に速くなりました。
どちらを選ぶべきかの判断軸
- 画面数・エンドポイント数が少ない初期フェーズ → レイヤーごと構成でシンプルに始めて問題ない
- 機能が10を超え、チームで並行開発する規模 → 機能ごと構成に早めに移行する
- 迷う場合は、将来的な機能追加を見越して機能ごと構成を初期から採用しておくと、後からのリファクタリングコストを避けられる
設定・共通処理の配置方針
config/パッケージに設定クラスを集約する
@Configurationクラスは、機能ごとのパッケージに散らばらせず、config/という専用パッケージに集約するのが定番です。
com.example.blog
├── config/
│ ├── SecurityConfig.java
│ ├── PasswordConfig.java
│ └── WebMvcConfig.java
├── order/
└── user/
Bean定義がどこに何個あるかを一箇所で見渡せるようになり、依存関係の全体像を把握しやすくなります。
common/またはshared/に横断的なユーティリティを置く
複数の機能パッケージから使われる共通クラス(例外ハンドラ・DTOの基底クラス・日時ユーティリティなど)は、common/パッケージにまとめます。
com.example.blog
├── common/
│ ├── exception/
│ │ ├── BusinessException.java
│ │ └── GlobalExceptionHandler.java
│ └── util/
│ └── DateTimeUtil.java
├── order/
└── user/
前回の記事で扱ったGlobalExceptionHandlerのような、アプリケーション全体を横断する仕組みは、このcommon/が自然な置き場所になります。
application.propertiesはプロファイルで整理する
デプロイの記事で扱ったプロファイル機能を使い、設定値は用途ごとに分割しておきます。
# application.properties(共通設定)
spring.application.name=blog
spring.jackson.time-zone=Asia/Tokyo
# application-prod.properties(本番固有)
spring.jpa.hibernate.ddl-auto=validate
logging.level.root=WARN
特にddl-autoは、開発環境ではupdateが便利でも、本番環境ではvalidate(スキーマの整合性を確認するだけで自動変更はしない設定)にしておかないと、意図しないテーブル変更が起きるリスクがあります。
テスト・ドキュメント面のベストプラクティス
テストコードもプロダクションコードと同じ構成規則に揃える
src/test/java配下のパッケージ構成は、src/main/java側と一致させておくのが基本です。
src/main/java/com/example/blog/order/OrderService.java
src/test/java/com/example/blog/order/OrderServiceTest.java
構成が一致していないと、「このクラスのテストはどこにあるか」を探す手間が発生し、テストの書き忘れにも気づきにくくなります。
API仕様はSpringDocで自動生成する
REST APIを提供する場合、springdoc-openapiライブラリを導入しておくと、コントローラのアノテーションからOpenAPI仕様とSwagger UIを自動生成できます。
// build.gradle
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
導入するだけで/swagger-ui.htmlにAPI仕様が生成され、手動でドキュメントを更新し続ける負担がなくなります。
コードとドキュメントが乖離しにくくなる点は、チーム開発において特に効果が大きいです。
応用・一歩先の使い方
モジュール分割を見据えたパッケージ設計
将来的にマルチモジュール(GradleのサブプロジェクトやMavenのマルチモジュール構成)への移行を見据えているなら、機能ごとのパッケージ構成をあらかじめ意識しておくと移行がスムーズです。
機能ごとにパッケージが独立していれば、そのパッケージをそのまま別モジュールとして切り出す作業の見通しが立てやすくなります。
コードフォーマッタとlinterをチームで統一する
Spotless(Gradleプラグイン)などのフォーマッタをプロジェクトに導入し、CIでフォーマットチェックを必須にしておくと、コードレビューで「インデントの指摘」のような本質的でない議論を減らせます。
// build.gradle
plugins {
id 'com.diffplug.spotless' version '6.25.0'
}
spotless {
java {
googleJavaFormat()
}
}
チームの規模が大きくなるほど、こうした機械的にチェックできるルールを人が目視で守る運用から切り離しておく効果は大きくなります。
まとめ
この記事のポイント
- 機能が増える見込みのプロジェクトは、初期から機能ごと(Feature First)のパッケージ構成を検討する
@Configurationクラスはconfig/、横断的な共通処理はcommon/に集約するapplication.propertiesはプロファイルで環境別に分割し、本番はddl-auto=validateにする- テストコードのパッケージ構成はプロダクションコードと一致させる
springdoc-openapiでAPI仕様書を自動生成し、コードとの乖離を防ぐ- Spotlessなどのフォーマッタ導入で、レビューの負担を軽減する
次に読むべき記事
Spring Boot編の設計・アーキテクチャカテゴリはここまでです。実践編として「Spring BootでシンプルなREST APIを作ってみる」もご覧ください。
タグ: Spring Boot, 上級者向け, 設計