システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る

パッケージ構成の考察

レイヤーで論理的な役割を整理したので、次はパッケージです。

パッケージ概要

フォルダ構成例

  boundedcontext
    ├─application
    │  ├─service
    │  │  └─hoge
    │  └─usecase
    ├─domain
    │  ├─model
    │  │  └─hoge
    │  └─rule
    ├─infrastructure
    │  └─datasource
    │      ├─entity
    │      └─repository
    │          └─hoge
    └─presentation
        └─hogeregister

Presentation

  • 画面(や コンポーネント)、スコープ(ConversationやFlow)の単位。
  • RestURIの単位。
  • 実装技術単位ではなく、インタフェース単位でフォルダを作成。
    (特定のフォルダ名ではないので、上図でも背景を白にしています)
  • Controller(Action)とForm(表現の実体)は基本的に同一フォルダにまとめる。
    ただし、複数コンポーネントで整理したい場合は、サブフォルダで分割。
    同様に同一スコープの単位でフォルダを作成しても良い。
    (登録→確認→完了を「XX登録」を親フォルダとした構成イメージ)
  • 命名:画面単位(Aggregate)はHogePage、部品はFugaForm
  • Scopeは基本はConversationScoped、参照系はRequestScopedでも良い。

Application

Presentation層からUsecaseまたはServiceが呼び出される。

Usecaseだけ、Serviceだけ、で実装をしても良いが、基本的にUsecaseを外部に公開してServiceを組み合わせて実装をする。

Usecase

  • Domainに属する情報を扱う
  • トランザクション境界
  • ユースケース記述に近しい実装
  • Actorを必ず記述する(Annotationで表現)
  • ServiceおよびDomain(Repository)により業務フローを表現する
  • コンテキストや主たる関心事の単位でフォルダ分割をして見通しを良くしても良い。
  • 単位は動詞
  • ScopeはRequestScoped

Service

  • Domainに属さない情報を扱う
  • Query(参照系)とCommand(登録系)
  • Usecaseから呼び出さない場合はCommandをトランザクション境界とする。
  • 単位は動詞
  • ScopeはAppliactionScoped

Infrastructure

ドメインモデルを永続化する具体的な実装。

DataSource、Messagingだけということではなく、よくある永続化の参考例。

他にFileSystemなど、永続化対象に応じてフォルダを作成する。

一般的な永続化であるDatasourceによる整理を行う。

DataSource

  • DomainのRepositoryの実装
  • entity、repository
    repository配下はAggregateRootModel単位のフォルダを更に作成
  • repositoryのScopeはAppliactionScoped

Domain

Model

  • ValueObjectとEntityによる関心事の実装
  • Repositoryインターフェースによる依存解決
  • AggregateRootModel単位でフォルダ作成
  • ScopeはDependant

Rule

  • Modelで表現するのに適切でないルールの実装
  • DDDのDomainServiceと同じ
  • コンテキストや主たる関心事の単位でフォルダ分割をして見通しを良くしても良い。
  • ScopeはDependant

考察

UsecaseとServiceの違い

一番の違いは、ドメインモデルを扱う主体かどうかです。

Actor(Role)を実装で明確に定義するか、しないか、というのも大きい違いです。

ActorはAnnotationで表現し、Interceptorで認可判定を行うイメージです。

一般的には認可判定はControllerで行うと思われますが、ユースケース記述にあわせて考えると、Application層で表現するのが筋が良さそうに思いました。

「要件・仕様」がUsecaseで、「実装機能・機能部品」がService という分類イメージです。

引数・戻り値についてUsecaseはドメインモデルで、ServiceはRowSetドメインではないモデル)というのも違いです。 単純な一覧表示のための参照情報である場合はServiceのQueryを使って処理をして Domainを用いたCRUDをしたい場合はUsecaseからDomainRepositoryを使って処理をします。

ということで、ServiceのCommandが使われるケースは基本的に無いか、限りなく少ないと考えていますが 「Usecaseとは言えないフローに登場する登録プロセス」というものは一定数あると思いますので、そういうときにCommandを使うことになると想定しています。 ただし、その場合も「関心事」としてまとめられないか考えて、Domainを中心としたUsecaseにすることを少なくとも1回は検討した方が良いと思います。

UsecaseとServiceは動詞

調べた限りでは「名詞(Entity名)Service」にしているケースが多いですが、「動詞」の方が良いと考えています。

理由はユースケース(およびユースケース記述)が動詞だからです。

アクター+動詞(Usecase)で意味を成しているのだから、やはりそちらの方がシックリくるのではないだろうか、と。

正直、細かすぎる気もするけれど、まぁこういう整理もありかな、と。

DataSourceのentityフォルダ配下はフラット

DBのテーブル自体がフラットなので、当該フォルダ配下も分類もしない。

これはNetBeansによる自動生成したJPAのEntity資産を必要以上に編集しないだけ、というのが本音。

DataSourceのRepositoryは ほぼDAO

集約のルートととしたDAO(CRUD)が基本。

でもCRUDだけではなくて、その他の「操作」も扱うリッチなDAO。

DomainServiceじゃなくてRuleと表現

Serviceという表現は紛らわしい。

それにDomainServiceの表現を調べると、ほとんど「ドメインオブジェクトの責務ではない、複数のドメインを扱うビジネスルール」に近しい表現がされており、それであれば「ルール」の方が紛らわしくない、という考えから。

パッケージで境界付けされたコンテキストを表現

パッケージは名前衝突をさけるためにプロダクトのURLを逆にする、くらいしか考えていなかったのですが、パッケージは境界付けされたコンテキストにするという発想をDDD関連の記事で読んで、随分と思考がスッキリしたので、それを採用しました。

パッケージ構成というか、フォルダ構成というか、そういうところです。

組織のドメイン.コンテキスト.レイヤー.詳細

モジュールの管理単位はコンテキストで集約。

共通部品とかインフラ層の抽象化したものとかは、フレームワークみたいな形で、別のコンテキストとしておくことで、自然と資産の分類がイイ感じになるのでは?と考えています。

なお、以前は

組織のドメイン.レイヤー.コンテキスト.詳細

という構成でした。 この構成だと、コンテキストがレイヤーの下であるため、発想が自然とモノリシックになってしまっていました。

ちょっとした違いかもしれませんが、実装によって発想が歪みにくくなる効果を期待しています。

Scopeについて

基本の整理はこちらをベースの考え方とします。

vermeer.hatenablog.jp

UsecaseのScopeについて

UsecaseについてRequestScopedとした点についての補足

としたためです。

一見、Usecaseはビジネスロジックであり、状態を保持することがないのでApplicationScopedでも良さそうなところですが、そうしてしまうと上述2つのApplicationScopedではないクラスのDIができません。
ServiceクラスはApplicationScopedにしている事例が多いため悩んだところですが、レイヤーとパッケージ構成の整理をした結論として そのようにしました。

DomainのScopeについて

そもそもDomain層のクラス自身が主体的にScopeを持つことは無いと考えています。
どうしてもDIによりDomainクラスを使用したい場合は、その利用側のScopeにあわせることになると思いますのでDependantとしました。
ただし、そのような設計となっている場合、DomainクラスそのものにScopeをつけるのではない方式で実装できないか再考した方が良いと思います。
(例えばDIではなく、Factoryにするなど)。

履歴

  • 2018/4/8 Scopeに関して追記
  • 2018/4/10 UsecaseとServiceに関して手直し