5
5

【Spring Boot】個人的に推奨するディレクトリ/ファイル構成

Last updated at Posted at 2024-06-01

挨拶

初めまして!株式会社アールピーシーの加藤です。
2024 年度から技術組織を立ち上げ、組織活動の 1 つとして情報発信していきます。
どうぞよろしくお願いします。

さて株式会社アールピーシーでは開発言語である Java を主軸としています。
今後、私からは Java を使った Web アプリケーションを通して様々な情報を紹介していく予定です。

開発言語や各種ライブラリは月日と共にアップデートがされていきます。
なのでそれらの紹介はほどほどにして、大きく変わることがないアーキテクチャや実装の考え方をメインで紹介していきます。

前置きが長くなりましたが、本日紹介するのはディレクトリ/ファイル構成です。
実際にアプリケーションを作る際、最初に行うのはこれらの検討ではないでしょうか。

解説の前に…

(2024/06/22追加)
解説にあたってRESTful API、Webサイト、バッチが登場しますが、本記事ではRESTful APIをメインに紹介します。Web サイトについては構成が似ているため割愛、バッチについては後日紹介予定です。

参考文献

NTT データグループ様 「TERASOLUNA Server Framework for Java (5.x) Development Guideline」
https://terasolunaorg.github.io/guideline/current/ja/

読者ターゲット

  • Java の Web アプリケーション開発経験がある方
  • MVC(Spring) モデルがわかる方(下記ドキュメントが参考になります)
    「リンク先:NTT データグループ様 TERASOLUNA Server Framework for Java (5.x) Development Guideline/2.2. Spring MVCアーキテクチャ概要」

  • RESTful API がわかる方(下記ドキュメントの「Overview」、「Architecture」が参考になります)

「リンク先:NTT データグループ様 TERASOLUNA Server Framework for Java (5.x) Development Guideline/5.1. RESTful Web Service」

  • Controller/Service/Repository のレイヤー分けがわかる方(下記ドキュメント参考になります)

「リンク先:NTT データグループ様 TERASOLUNA Server Framework for Java (5.x) Development Guideline/2.4. アプリケーションのレイヤ化」

  • データべースのリレーションがわかる方

前提

  • Java のフレームワークである Spring Boot を利用する
    • (2024/5 時点)Java のフレームワークといったらこれ
    • 導入するライブラリは最小限の【spring-boot-starter-web】のみ
  • ドキュメントファーストよりソースファースト
    • ドキュメント更新が追い付かない、どこにあるか分からない、増えすぎて分からないを想定してソースファーストの考え
  • 極力シンプルな構成
    • クリーンアーキテクチャの思想はほどほどに
  • ディレクトリ配下のディレクトリ/ファイルは合計で 10 以下になるよう意識する
    • 認知負荷を下げる
  • 関係性のあるファイル同士は極力近くに配置する
    • 認知負荷を下げる
  • 様々なアプリケーションで共用できるように 1 つのプロジェクトを小さく細かく分ける
    • アプリケーションごとに同じ実装を作るなんて勿体ない
  • test フォルダ配下は本記事では割愛

題材

  • プチ EC サイト
    • ユーザと商品とショッピングカートの概念がある
    • ユーザ、商品の取得、登録、更新、削除が可能
    • ショッピングカートの取得、登録、削除が可能
  • このサイトには RESTful API、Web サイト、バッチが存在し、同一のデータベース、外部 API を利用する

DB 構成

  • ユーザ:users
  • 商品:goods
  • ショッピングカート:user_goods
    ER図.png

エンドポイント

パス HTTP メソッド 用途
/users GET ユーザ情報取得
/users POST ユーザ情報登録
/users PATCH ユーザ情報更新
/users DELETE ユーザ情報削除
/goods GET 商品情報取得
/goods POST 商品情報登録
/goods PATCH 商品情報修正
/goods DELETE 商品情報削除
/users/[id]/goods GET ショッピングカート情報取得
/users/[id]/goods POST ショッピングカートに商品追加
/users/[id]/goods DELETE ショッピングカートから商品削除

これらの情報をもとにまずは何も考えずにディレクトリとファイルを配置してみましょう。

ディレクトリ/ファイル構成とりあえず作ってみた

RESTful API、Web サイト、バッチのアプリケーションを[root ディレクトリ]直下に作成してマルチプロジェクトにします。(マルチプロジェクトにする方法はたくさん紹介されているのでここでは割愛)

アプリケーション プロジェクト名
RESTful API [domain]-api
Web サイト [domain]-web
バッチ [domain]-batch

※[domain]部分はシステム名などに置き換えてください(例:smartshopping など)

[rootディレクトリ]
├── [domain]-api
│   └── src
│      ├── main
│      │   ├── java
│      │   │   └── [パッケージ]
│      │   │       ├── config                             # Bean定義など設定
│      │   │       │       └── etc...
│      │   │       ├── constant                           # 定数
│      │   │       │       ├── DateConstant.java
│      │   │       │       └── etc...
│      │   │       ├── controller                         # コントローラー層
│      │   │       │       ├── GoodsController.java       # 商品に関する処理のエンドポイント
│      │   │       │       ├── UsersController.java       # ユーザとショッピングカートに関する処理のエンドポイント
│      │   │       │       └── etc...
│      │   │       ├── model                              # ドメインオブジェクト or DTO
│      │   │       │       ├── GetUserRequest.java        # ユーザ情報取得エンドポイントリクエストモデル
│      │   │       │       ├── GetUserResponse.java       # ユーザ情報取得エンドポイントレスポンスモデル
│      │   │       │       ├── etc...                     # 他にもエンドポイントのモデルがたくさん!
│      │   │       │       ├── GetUserRequestModel.java   # ユーザ情報取得サービスリクエストモデル
│      │   │       │       ├── GetUserResponseModel.java  # ユーザ情報取得サービスレスポンスモデル
│      │   │       │       ├── etc...                     # 他にもサービスのモデルがたくさん!
│      │   │       │       ├── GoodsEntity.java           # 商品テーブルEntity
│      │   │       │       ├── UserGoodsEntity.java       # ショッピングカートテーブルEntity
│      │   │       │       ├── UsersEntity.java           # ユーザテーブルEntity
│      │   │       │       └── etc...
│      │   │       ├── repository                         # リポジトリ層(データアクセス層)
│      │   │       │       ├── GoodsRepository.java       # 商品テーブルリポジトリ
│      │   │       │       ├── UserGoodsRepository.java   # ショッピングカートテーブルリポジトリ
│      │   │       │       ├── UsersRepository.java       # ユーザテーブルリポジトリ
│      │   │       │       ├── ExternalApiRepository.java # 外部APIリポジトリ
│      │   │       │       └── etc...
│      │   │       ├── service                            # サービス層
│      │   │       │       ├── GoodsService.java          # 商品に関するビジネスロジック
│      │   │       │       ├── UsersService.java          # ユーザとショッピングカートに関するビジネスロジック
│      │   │       │       └── etc...
│      │   │       ├── util                               # 共通処理
│      │   │       │       └── DateUtil.java
│      │   │       └── ApiApplication.java
│      │   └── resources
│      │       ├── application.properties
│      │       └── messages.properties
│      └── test
│
├── [domain]-batch
│   └── ※後日紹介予定
│
├── [domain]-web
│   └── ※[domain]-apiと似た構成のため割愛
│
├── build.gradle
└── settings.gradle

ざっくり構成紹介

(2024/06/22追加、2024/6/27 Webサイトとバッチの構成図を追加)
image.png

↑ の課題だと思う点(個人的に

  • 定数、共通処理はシステム全体で使うものと一部アプリケーションでしか使わないものが混在する
  • Controller や Serviceがユーザ機能、商品機能に紐づいているのでそれらに関する実装がひとつのファイルに一極集中してしまいステップ数が膨大になりがち
  • ビジネスロジック/リポジトリ層が各アプリケーションに包括されているので同一の処理であっても別アプリケーションから使えない
  • 各層の実装とドメインオブジェクト or DTO が別々のパッケージで探しづらい
  • 機能が増えていくにつれて model 配下のファイル数が膨大になる
  • ファイル数が膨大になることでお目当てのファイルが探しづらい、ディレクトリツリー展開したときにスクロールが長くて認知負荷が高い

ディレクトリ/ファイル構成直してみた

(2024/06/13修正:build.gradle,settings.gradleをrootディレクトリ配下に配置、[domain-api]配下にあったsetting.gradleを削除)

[rootディレクトリ]
├── [domain]-api
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   └── api
│   │   │   │       ├── constant
│   │   │   │       │   └── etc...
│   │   │   │       ├── controller
│   │   │   │       │   ├── v1
│   │   │   │       │   │   ├── goods
│   │   │   │       │   │   │   ├── payload
│   │   │   │       │   │   │   │   ├── GetGoodsRequest.java
│   │   │   │       │   │   │   │   ├── GetGoodsResponse.java
│   │   │   │       │   │   │   │   └── etc...
│   │   │   │       │   │   │   └── GoodsController.java
│   │   │   │       │   │   ├── usergoods
│   │   │   │       │   │   │   ├── payload
│   │   │   │       │   │   │   │   ├── GetUserGoodsRequest.java
│   │   │   │       │   │   │   │   ├── GetUserGoodsResponse.java
│   │   │   │       │   │   │   │   └── etc...
│   │   │   │       │   │   │   └── UserGoodsController.java
│   │   │   │       │   │   └── users
│   │   │   │       │   │       ├── payload
│   │   │   │       │   │       │   ├── GetUsersRequest.java
│   │   │   │       │   │       │   ├── GetUsersResponse.java
│   │   │   │       │   │       │   └── etc...
│   │   │   │       │   │       └── UsersController.java
│   │   │   │       │   └── v2
│   │   │   │       ├── util
│   │   │   │       │   └── etc...
│   │   │   │       └── ApiApplication.java
│   │   │   └── resources
│   │   │       ├── application.properties
│   │   │       └── messages.properties
│   │   └── test
│   │       └── etc...
│   └── build.gradle
│
├── [domain]-batch
│   └── etc...
│
├── [domain]-core
│   ├── src
│   │   ├── main
│   │   │   └── java
│   │   │       └── core
│   │   │           ├── constant
│   │   │           │   └── DateConstant.java
│   │   │           └── util
│   │   │               └── DateUtil.java
│   │   └── test
│   │       └── etc...
│   └── build.gradle
│
├── [domain]-db-repository
│   ├── src
│   │   ├── main
│   │   │   └── java
│   │   │        └── repository
│   │   │            ├── goods
│   │   │            │   ├── entity
│   │   │            │   │   ├── custom
│   │   │            │   │   │   └── SearchRequestEntity.java
│   │   │            │   │   └── GoodsEntity.java
│   │   │            │   └── GoodsRepository.java
│   │   │            ├── usergoods
│   │   │            │   ├── entity
│   │   │            │   │   └── UserGoodsEntity.java
│   │   │            │   └── UserGoodsRepository.java
│   │   │            └── users
│   │   │                ├── entity
│   │   │                │   └── UsersEntity.java
│   │   │                └── UsersRepository.java
│   │   └── test
│   │       └── etc...
│   └── build.gradle
│
├── [domain]-service
│   ├── src
│   │   ├── main
│   │   │   └── java
│   │   │       ├── logic
│   │   │       │   ├── GoodsLogic.java
│   │   │       │   ├── UserGoodsLogic.java
│   │   │       │   └── UsersLogic.java
│   │   │       └── service
│   │   │           ├── goods
│   │   │           │   ├── model
│   │   │           │   │   ├── GetGoodsRequestModel.java
│   │   │           │   │   ├── GetGoodsResponseModel.java
│   │   │           │   │   └── etc...
│   │   │           │   └── GoodsService.java
│   │   │           ├── users-goods
│   │   │           │   ├── model
│   │   │           │   │   ├── GetUserGoodsRequestModel.java
│   │   │           │   │   ├── GetUserGoodsResponseModel.java
│   │   │           │   │   └── etc...
│   │   │           │   └── UsersGoodsService.java
│   │   │           └── users
│   │   │               ├── model
│   │   │               │   ├── GetUsersRequestModel.java
│   │   │               │   ├── GetUsersResponseModel.java
│   │   │               │   └── etc...
│   │   │               └── UsersService.java
│   │   └── test
│   │       └── etc...
│   └── build.gradle
│
├── [domain]-web
│   └── etc...
│
├── [external-domain]-api-repository
│   └── etc...
│
├── build.gradle
└── settings.gradle

アーキテクチャ

image.png

  • マルチプロジェクト構成(青枠の部分がプロジェクト)
    (2024/06/27備考を追記)
アプリケーション プロジェクト名 備考
RESTful API [domain]-api [domain-core、[domain]-service、[domain]-db-repository、[external-domain]-api-repositoryのプロジェクトをimport
Web サイト [domain]-web 同上
バッチ [domain]-batch 同上
共有リソース、ビジネスロジックを含まないロジック [domain]-core
ビジネスロジック [domain]-service [domain]-coreのプロジェクトをimport
DB アクセス [domain]-db-repository 同上
外部 API アクセス [external-domain]-api-repository 同上
  • Service 層をプロジェクト化し、複数のアプリケーションでビジネスロジックが共用可
  • Repository 層をプロジェクト化し、複数のアプリケーションで DB/外部 API 呼出といったデータアクセスが共用可
  • 各層のインターフェースは図の通り、payload、model、entity で行う

解説

それでは各プロジェクトの解説をします。

補足

  • アーキテクチャでは [external-domain]-api-repisotry の登場がありますが、[external-domain]-api-repository は [domain]-db-repository とほぼ同様の構成のため割愛(接続先が DB か 外部 API かの違い

  • [domain]-core、[domain]-db-repository、[domain]-service、[external-domain]-api-repository は NEXUS などのパッケージリポジトリとしての採用も想定しています

[domain]-core(好みでbase or common でも可)

概要

  • システム全体で使うものを定義します
  • アノテーション、列挙型など、この層に配置するディレクトリは後日紹介予定です。今回は一般的?なディレクトリのみに限定しています

constant

  • 定数を記述したファイルを配置します(日付フォーマットなどシステム全体で使うもの

util

  • 共通関数を記述したファイルを配置します(日付ユーティリティなどシステム全体で使うもの

[domain]-api

概要

  • controller + α の構成です
  • controller 以外は [domain]-core と「同一」ディレクトリの配置します。配置するファイルも「同様」ですが、[domain]-core はシステム全体に対し、ここに配置するのは自アプリケーション内で使うもの限定となります

controller

  • パッケージ命名

    • RESTful API のエンドポイントである Controller 層は uri のパスごとにパッケージを配置します

    • 単語同士はパッケージ命名規則に基づき小文字で結合します

    • REST API 呼び出し側の状況によって新旧のエンドポイントが混在するケースがあるので v1,v2 とバージョンを分けます

      uri パッケージ
      /v1/users api/controller/v1/users
      /v1/goods api/controller/v1/goods
      /v1/users/[id]/goods api/controller/v1/usersgoods
      /v2/users/[id]/goods api/controller/v2/usersgoods
  • 説明

    • パッケージ配下には xxxController.java と payload パッケージを配置します
    • payload 配下には RESTful API のリクエストおよびレスポンスの項目を定義したドメインオブジェクト or DTO を配置しますが、それぞれ項目がない場合は配置不要です
      • 同一 uri で HTTP メソッドが分かれる場合は Get~、Post~、Delete~とファイル名の先頭に HTTP メソッドを付けます

[domain]-service

概要

  • logic と service の構成です、ただしここでしか利用しない定数、共通処理などあれば別途パッケージを作ってください

logic

  • パッケージ配下にはビジネスロジックを実装したファイルを配置します
  • 独自に細かく分けても良いですが、シンプルにしたい場合はファイルの作成単位は service と合わせます

service

  • パッケージ命名

    • Controller と同じパッケージ配置です

    • ただし v1,v2 といったバージョンの概念はありません

      uri Controller パッケージ Service パッケージ 備考
      v1/users api/controller/v1/users service/users
      v1/goods api/controller/v1/goods service/goods
      v1/users/[id]/goods api/controller/v1/usersgoods service/usersgoods メソッド名で区別します
      v2/users/[id]/goods api/controller/v2/usersgoods service/usersgoods メソッド名で区別します
  • 説明

    • パッケージ配下には xxxService.java と model パッケージを配置します
    • model 配下には service の引数および戻り値の項目を定義したドメインオブジェクト or DTO を配置しますが、引数が規定数(例:3 つ)以内で直接引数を渡すケースや戻り値がない、もしくは 1 つだけの場合は配置不要です
    • xxxController.java と xxxSevice.java、yyyController.java と yyyService.java などと Controller と 1:1 になるように作成します
    • service は logic の組み合わせで実装します。ビジネスロジックは実装しませんがビジネスロジックを呼び出す条件だけは実装します
      • 通常は Controller 層からビジネスロジックを共用する目的で同一の service を呼び出すケースが多いかと思います
      • システム運用が長期化すると一部の機能や画面限定のビジネスロジックが service に入り込み、使い分ける必要が出てきた、Controller 層から引数を渡して service で分岐条件を付けて回避した、などと共用を想定して実装したが破綻した。というのを見てきました
      • 上記の理由からビジネスロジックを呼ぶ層(service)とビジネスロジック層(logic)を分けています。そうすることで少なくともController層の影響は少なくなる想定です

ASIS

(2024/06/22追加)
image.png

TOBE

(2024/06/22追加)
image.png

[domain]-db-repository、[external-domain]-api-repository

概要

  • repository のみの構成です、ただしここでしか利用しない定数、共通処理などあれば別途パッケージを作ってください

repository

  • パッケージ命名

    • テーブルやエンドポイントごとにパッケージを配置します
    • ハイフンやアンダーバーはパッケージ命名規則に基づき小文字で結合します
    テーブル パッケージ
    users repository/users
    goods repository/goods
    user_goods repository/usergoods
  • 説明

    • パッケージ配下には xxxRepository.java と entity パッケージを配置します
    • entity 配下にはテーブルカラムや外部 API のインターフェースのプロパティを定義したファイルを配置します
      • 商品の検索で商品名だけ使うといった独自 Entity を作る場合は更に custom パッケージを配置し、その配下に配置します
      • テーブル同士を結合するケースのおける実装や entity の配置は親テーブルのパッケージ配下にします

最後に

本記事では語り切れていないことが多いです。今後投稿する記事で補完を考えています。次回は[domain]-core に配置するディレクトリ/ファイルについて深堀して紹介する予定です。

5
5
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
5
5