挨拶
初めまして!株式会社アールピーシーの加藤です。
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 構成
エンドポイント
パス | 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サイトとバッチの構成図を追加)
↑ の課題だと思う点(個人的に
- 定数、共通処理はシステム全体で使うものと一部アプリケーションでしか使わないものが混在する
- 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
アーキテクチャ
- マルチプロジェクト構成(青枠の部分がプロジェクト)
(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
TOBE
[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 に配置するディレクトリ/ファイルについて深堀して紹介する予定です。