RACCOON TECH BLOG

株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。

レイヤードアーキテクチャで新規アプリのパッケージ構成を考えた話

技術部のやすだです。

先日までかかわっていたPJで既存アプリの機能をSpring Bootベースの新規アプリに移植する作業を担当していました。
新規アプリなのでパッケージ構成の検討で色々悩みつつ反省もしつつ、ひとまずは落としどころが見つかったのでその辺りの話をまとめようかと思います。

要件・仕様

弊社の決済事業[Paid]での決済情報登録に関する一部機能を提供できるWebAPIを作成します。
機能の概要は以下の通りです。

インターフェースとしては、いずれの機能もWebUIは提供せず、jsonでのやり取りを行います。
エンドポイントについては、各機能毎に個別のエンドポイントを設定します。

パッケージ構成

少人数ではありますが複数人で開発を行う前提となっています。
各クラスの責務や依存関係についてある程度の共通認識を取る必要があったため、オーソドックスなレイヤードアーキテクチャをベースにしています。
LayeredArchitectureImage

パッケージ構成は以下のようにしました。
()内はアーキテクチャ上で対応しているレイヤーです。
実際にはadviceやconfigなどのパッケージもありますが、説明用に割愛しています。

├─app(Presentation/Application)
│ ├─order
│ │ ├─controller(Presentation/Application)
│ │ ├─factory(Presentation/Application)
│ │ └─form(Presentation)
│ │   ├─request
│ │   └─response
│ ├─member
│ │ ├─controller
│ │ ├─factory
│ │ └─form
│ │   ├─request
│ │   └─response
│ └─その他関心単位のパッケージ
└─domain(Domain/Infrastructure)
  ├─model(Domain)
  │ ├─dto
  │ └─entity
  ├─query(Domain/Infrastructure)
  ├─service(Domain)
  └─repository(Domain/Infrastructure)

各パッケージの説明

app

Presentation層とApplication層をまとめています。
仕様上インターフェースはjsonのみということもあり、なるべく構成をシンプルにすることを重視しています。
ただし、機能毎に個別のエンドポイントを利用することもあり、アプリケーションの関心単位でのパッケージ分割を行っています。

app/controller

controllerをまとめたパッケージです。
Presentation層とApplication層をまとめているので、下記の責務を持ちます。

app/factory

リクエストパラメータ->ドメインオブジェクトとドメインオブジェクト->レスポンスパラメータの変換を責務とするクラスをまとめたパッケージです。

app/form

リクエストパラメータとレスポンスパラメータのDTOをまとめたパッケージです。

domain

Presentation層とApplication層同様に、こちらもシンプルな構成にしたいという考えからDomain層とInfrastructure層をまとめています。
今回はアプリの規模が小規模で開発人員が少人数なこともありドメインロジックが散らばってしまうようなケースは防げるかと判断しました。

domain/model

ドメインオブジェクトをまとめたパッケージです。
Entityのみで表現しようとするとどうにも違和感が出がちなので、そんな場合はムリせずDTOを使う方針にしています。
パッケージ名はDomainObjectにしちゃってよかったような気もしますね。

domain/query

Repositoryで対応するには検索条件が複雑になってしまう時に助かるQueryServiceのパッケージです。
今の構成だとQueryServiceから返すDTOもdomain/model以下にまとめていますが、DTOは特定のコントローラ(Application)以下のパッケージにある方が自然な気もちょっとします。

domain/repository

Entityの永続化を責務とするRepositoryをまとめたパッケージです。

domain/service

ドメインロジックをまとめたパッケージです。

反省点と落としどころ

Presentation層とApplication層をまとめたことは、当初の想定通り構成のシンプルさというメリットにつながりました。

対して、Domain層とInfrastructure層をdomainパッケージにまとめたことは大きなデメリットにつながることになりました。
開発中の仕様変更に伴って、複数テーブルを操作する処理を1つのrepositoryクラスで記述するケースやカラムのデータ更新の要否をrepository内で判定してしまうケースなどが出てくるようになります。
開発人員間での情報共有等は都度行っていましたが、それでもドメインロジックに閉じるべき責務が徐々にrepositoryに漏れていくというあるあるを目の当たりにして「なるほど。。こうなるのか。。。」という経験をすることになります。
アプリケーションの規模や開発人員の状況によらず、Domain層とInfrastructure層はパッケージレベルで分割してドメインロジックが散りにくいような構成にしておいた方が良かったなと思いました。

今後の対応として、ドメインロジックが漏れにくい構成にする=ドメインロジックがserviceパッケージにまとまりやすい構成へ修正することを考えています。
具体的には、既存のqueryとrepositoryをインターフェースとしてdomainパッケージ直下に残しつつ、実装のみInfrastructure層に移すことでドメインロジックが漏れにくくできるかと考えています。
対応を実施すると下記のようなパッケージ構成になるかと思います。

├─app(Presentation/Application)
│ ├─order
│ │ ├─controller
│ │ ├─factory
│ │ └─form
│ │   ├─request
│ │   └─response
│ └─その他関心単位のパッケージ
├─domain(Domain)
│  ├─model
│  │ ├─dto
│  │ └─entity
│  ├─query
│  ├─service
│  │ └─serviceImpl
│  └─repository
└─infrastructure(Infrastructure)
   ├─queryImpl
   └─repositoryImpl

おわりに

短くまとめると、シンプルに保守しやすく作っていたつもりが、実はアンチパターンになっていたという話でした。
今回はDBに関連する実装をinfrastructureパッケージにまとめる対策を行う予定ですが、
今後より洗練された構成にできたときや新しい気づきがあった時には、改めてこちらで発信できればと思います。
それでは、よい開発を!

一緒にラクーンのサービスを作りませんか? 採用情報を詳しく見る

関連記事

運営会社:株式会社ラクーンホールディングス(c)2000 RACCOON HOLDINGS, Inc