- ユーザとの相互作用の境界となる層
- アプリケーション層の一部なのか別個のレイヤなのか諸説あり
- ユーザーに情報を提示して、ユーザーの操作やコマンドの解釈を担うレイヤー。 DDDでは、このレイヤーにビジネスロジックを組む込むことを皮肉を込めて「利口なUI」というアンチパターンとしており、避けるべきパターンである。
- ドメインオブジェクトを操作することで、ソフトウェアが果たすべき仕事を実現する層
- アプリケーションが行うことになっている仕事(ユースケース)を定義し、その仕事をドメインレイヤーのオブジェクトが解決するように指揮(orchestrate)するレイヤー。このレイヤーもビジネスロジックは含まず、実際の処理はドメインレイヤーに委譲して調整役を担う。
- ビジネス上の概念を表現する層
- ビジネスの概念と、ビジネスルールおよびビジネスが置かれた状況に関する情報を表現するレイヤー。このレイヤーがアプリケーションの核心となるレイヤーで、モデルが息づく場所である。但し、技術的な関心事についての実装はインフラストラクチャレイヤーに委譲する。
-
上の3層を支える技術的な基盤となる層
-
永続化、 メッセージング、ロギング等
-
メッセージングには、通信プロトコルによって種類がある。(HTTP、UDP、AMQP、FTPなど。)
-
上述のレイヤーを支える一般的な技術的機能を提供するレイヤー。データの永続化に関する技術的な機能(トランザクション管理やO/Rマッパー)や、ユーザーインターフェイスのレンダリング機能などが該当する。
-
クラスをドメイン層に置くべきか、インフラストラクチャ層に置くべきか、判断する基準は、ドメイン層のクラスに依存する場合はドメイン層へ、ドメイン層に依存しない場合は、インフラストラクチャ層に置くべき?
https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap2.html http://www.shigemk2.com/entry/reactive_shinjuku_domain_event http://enterprisegeeks.hatenablog.com/entry/2016/07/25/081127
-
オブジェクトの中には、アイデンティティをもつもの(エンティティ)が存在する。エンティティは、アイデンティティが同じならば、属性が異なっていても同一のものとして扱わなければならない。逆に、属性がまったく同じでも、アイデンティティが違えば当然別エンティティとなる。エンティティはドメインモデルの主役であり、そのライフサイクルを考えることが重要になる。ドメインモデルの中において、エンティティの同一性をどうやって判断するかを定義しなければならない。
-
ドメインレイヤーに登場する構成要素。アプリケーション上で同一性の識別が必要なオブジェクト。つまり、アプリケーション上でオブジェクトの一意性を意識して扱う必要があるオブジェクトである。以下のようなエンティティは業務を遂行するうえで一意に特定する必要がある。
物、サービス(商品、在庫) 人、組織(顧客) 場所(配送先、倉庫、保管場所) 取引(注文、在庫引当) 仮に同姓同名別人の複数の「顧客」がいたとしたら、その顧客達は別々のインスタンスとして扱われるべきで、名称が一緒だからといってその存在を一括りにしてしまうと、注文や配送が目茶苦茶になってしまう。逆に、オブジェクトごとの同一性を意識しない単なる数量や金額といった同値性で語られる概念は、この後に紹介するバリューオブジェクトとなる。
-
エンティティとは逆に、たとえば「色」や「量」のように、その属性だけが重要で、アイデンティティを考えることに意味のないオブジェクトもある。そうしたオブジェクトは、値オブジェクトに分類する。値オブジェクトとは、事物の性質を表現するものである。値オブジェクトは状態を変更できないもの(immutable)として扱う。エンティティの複雑さに専念できるように、値オブジェクトはシンプルな設計に保つようにする。(この値オブジェクトは、PofEAAのValue Objectパターンと同じものを表している)
-
ドメインレイヤーに登場する構成要素。ドメイン上に登場する値そのものをオブジェクト化した同一性を持たない小型のオブジェクト。以下のようなオブジェクトは、プリミティブ型や標準型(String型やDate型)で定義するのが一般的である。
数量 金額 コード 日付 区分 これらを「注文番号」クラス、「注文数量」クラス、「配送予定日」クラス、「商品コード」クラスといった業務上の意味を宿らせたクラスとして明示的に定義することで、その値自身がもつ知識をバリューオブジェクトに凝縮させることができ、ソフトウェア上の表現としてもより明確にできる。
-
ドメインで扱う概念の中には、1つの機能や処理が単体で存在していて、もの(オブジェクト)として扱うのが不自然なものもある。そうしたものは、サービスという形でユビキタス言語に組み込む。サービスは基本的に状態をもたない(stateless)。(PofEAAのService Layerパターンとは異なる概念なので注意。Service LayerパターンはDDDのアプリケーション層に相当するものを言っているが、DDDのサービスはドメインモデルの中にあるサービス的なものを指している。邦訳はないが、Fowler氏の記事「Evansの分類」も参考になる)
-
最初に、「サービスとは、モノとして扱うと不自然なものをサービスに分類する」と触れましたが、エンティティやバリューオブジェクトというモノに分類できないような振る舞いを切り出す場所と考えるとよいでしょう。つまり、そういう振る舞いを分類する時に、例外的、消極的に使うモデルと考えてください。
-
ドメインレイヤーに登場する構成要素。 エンティティ(物、人、場所、取引など)にも、バリューオブジェクト(値そのもの)にも属さない、ドメイン上のビジネスルールやアクションを提供するサービス。 これらは、エンティティやバリューオブジェクトのように実体のモノとして捉えるのは不自然で、ビジネスルールにもとづいた計算や判定といったアクションを担うサービスとするのが適切である。
例えば今回のドメインモデルで、商品の価格を算定しようとした場合、標準価格だけならばエンティティから簡単に取得できるが、まとめ買いによる割引や期間限定での割引まで考慮しようとすると簡単には取得できない。 実際の販売価格を決定するためには、諸々のビジネスルールを複合的に加味する必要があるが、価格の算定方法をモノとして定義するのは不自然なため、ドメインサービスとして定義する。 この算定方法が至るところに散在すると、コード重複、メンテナンス性低下、価格差異発生などのバグの温床になるため、価格決定に関する知識は、このドメインサービスに凝縮させて外に流出させてはいけない。
ドメインサービスの提供粒度は、アプリケーションサービスの様にそれ単体でユースケースを満たすほど大粒ではなく、そのドメイン固有の知識範囲に特化したサービスとなる。また、ドメインサービスは、計算や判定を行う上で複合的な知識が必要となることが多く、大抵の場合は複数のエンティティやバリューオブジェクトを束ねた中粒のサービスを提供することになる。 逆に言うと、エンティティ単体やバリューオブジェクト単体の知識で提供できるメソッドがあれば、それはドメインサービスではなく、エンティティやバリューオブジェクトに記述すればよい。
- アプリケーションレイヤーに登場する構成要素。ドメインレイヤーの大小様々な知識を指揮(orchestrate)して、ユーザーに対して意味のある単位の機能を提供するサービス。ユースケースに登場してくるような機能は、アプリケーションサービスで表現する。しかし実際のビジネスロジックの記述はドメインレイヤーの部品に委譲して、アプリケーションサービス自体はドメインレイヤーの部品の調整に徹する。
- モジュールの仕組み(Javaのパッケージや.NETの名前空間など)は、プログラミングにおいては誰もが使っている。人間が一度に考えられる物事の量には限界があるので、概念についても同じようにモジュール分割が必要である。プログラミングの場合と同じく、ドメインモデルのモジュール分割においても高凝集/低結合が重要な目安になる。関連し合う概念同士はひとつにまとめ(高凝集)、かつ一度に考えなければいけない範囲は最小限にする(低結合)。モジュールの名前も、ユビキタス言語の重要な一要素として扱う。モジュールもコードと同様、リファクタリングによってアジャイルに進化させていかなければならない。
-
オブジェクトのライフサイクルを設計するには、まずライフサイクルの単位となるオブジェクトのまとまりを考えなければならない。たとえば「注文」と「注文明細」のように、関連するオブジェクトのグループは集約として扱い、集約を単位としてモデルの他の要素との境界を明確に分ける。集約の中から1つエンティティを選んで、それを集約のルート(root)とする(先ほどの例では、「注文」がルートになる)。1回きりの処理で参照が破棄されるような特別な場合を除いて、外部から参照できるのはルートだけで、中のオブジェクトに対する処理はすべてルートが中継する。こうすることで、集約内のモデルの一貫性を維持することができる。
-
ドメインレイヤーに登場する構成要素。 密接に関連のあるエンティティやバリューオブジェクト同士を集約したオブジェクトのクラスタ。
例えば今回のドメインモデルにおける「注文ヘッダ」-「注文明細」の関連セットは、アプリケーションの中でそれぞれ個々で扱われるよりも「注文」という1セットで扱われることが多いだろう。注文商品の明細が分からない注文ヘッダや、注文番号が分からない注文明細だけあっても役に立たないので、結局は都度都度 お互いの関連を辿る羽目になる。
こういった関連をカプセル化してアクセスを単純化する役割を担うのがアグリゲートである。
アグリゲートをきる場合、アグリゲート内の1つのエンティティをアクセスポイント(アグリゲートルート。今回だと「注文ヘッダ」が自然)として定めて外部に公開し、アグリゲート内の入り組んだ関連パスを隠蔽することで、外部からはアグリゲートルートを通じてアグリゲート内の情報に透過的にアクセス出来るようになる。
- オブジェクトや集約の生成処理はそれ自体複雑になりうるため、ファクトリを導入して生成処理をカプセル化*2する。オブジェクトの生成そのものがドメインモデル上で重要な意味をもつことは(ほとんど)ないため、ファクトリはドメインモデルの一部ではない。あくまで、ドメインの設計上必要な一要素、という位置付けになる。*2 本来「カプセル化」とは単に属性や操作を1つにまとめることを表す概念で、実装の詳細を外部から隠す概念である「情報隠蔽」とは異なります。しかしEvansはこの2つを混同して使っていて、カプセル化という言葉を情報隠蔽の意味でも使っていることに注意してください。
-
ファクトリにより生成されたドメインオブジェクトは、役目を終えて破棄されるまでは生存する。生存期間の途中で、ほとんどの場合はDBなどにいったん永続化される。DBへの永続化や問い合わせ処理の複雑さによって、ドメインモデルが汚染されないようにするため、リポジトリという永続化/問い合わせ専用オブジェクトをドメイン設計に導入する(ファクトリ同様、ドメインモデルの一部ではない)。リポジトリを使う側からは、ドメインモデルがあたかもメモリ上にコレクションとして存在しているかのように見える。リポジトリは、モデル中でグローバルなアクセスが必要なエンティティ(集約の場合はそのルート)毎に、1つ用意される。
-
ドメインレイヤーおよびインフラストラクチャレイヤーに登場する構成要素。エンティティやアグリゲートの保管(データストアへの永続化やキャッシュへの追加など)と、保管済みのエンティティやアグリゲートの取り出しを担う役割を持たせる。その名のとおり、まさにオブジェクトの貯蔵庫である。
尚、オブジェクトの保管に関する技術要素については、RDBを利用する場合もあれば、NoSQLを利用する場合もあるが、これらの技術要素に関するコードについてはインフラストラクチャレイヤーで実装(例えば、SQLのクエリコードなど)して、ドメインレイヤーでは技術要素を意識することなく、ドメインの言葉でオブジェクトの保管・取り出しを行う。
-
Repositoryクラスをインフラストラクチャ層に配置した場合、Repositoryがドメイン層に属するEntityを返却することを考えると、インフラストラクチャ層にあるクラス(Repository)が、ドメイン層にあるクラス(Entity)に依存している言える。通常、ドメイン層は、インフラストラクチャ層に依存しているが、その上、インフラストラクチャ層がドメイン層に依存してしまうと、レイヤー同士が双方向の依存関係をもち、返って、複雑さを招くのではないだろうか。
-
RepositoryとEntity、両方をInterfaceとImplementationに分け、Interfaceをドメイン層におき、Implementationをインフラストラクチャ層におく。InterfaceであるRepositoryは、InterfaceであるEntityに依存し、一方で、ImplementationであるRepositoryはImplementationであるEntityに依存するので、ドメイン層に依存せず、インフラストラクチャ層の内部で完結する。
-
Imaplementationは、Interfaceに依存しているので、依存関係の双方向性は解決したとはいえないか。
- isXxx()のような真偽値を返すメソッドからなるビジネスルールは、通常のエンティティや値オブジェクトには上手く割り当てられない。ビジネスルールをドメインモデル上で表現するには、論理学でいう「述語」(predicate)のような役目をする特殊な値オブジェクトを導入する。これがすなわち仕様である。仕様は一般に、次の3つを規定する。
オブジェクトの妥当性検証(validation) オブジェクトの選別条件(selection) オブジェクトの生成条件(creation)
- https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap2.html
- http://d.hatena.ne.jp/j5ik2o/20101229/1293584046
- http://enterprisegeeks.hatenablog.com/entry/2016/07/25/081127
sf2-ddd
├── app
├── bin
├── build
├── lib
├── src
│ └── __VendorPrefix
│ ├── Application
│ │ └── __DomainNameBundle
│ │ ├── Command
│ │ │ └── __Command.php
│ │ ├── Controller
│ │ │ └── __Controller.php
│ │ ├── DependencyInjection
│ │ │ ├── Configuration.php
│ │ │ ├── __CompilerPass.php
│ │ │ └── __Extension.php
│ │ ├── Form
│ │ │ ├── Extension
│ │ │ │ └── __FormExtension.php
│ │ │ ├── Model
│ │ │ │ └── __FormDataModel.php
│ │ │ └── Type
│ │ │ └── __EntityType.php
│ │ ├── Page
│ │ │ └── __Page.php
│ │ ├── Resources
│ │ │ ├── config
│ │ │ │ ├── routing.yml
│ │ │ │ └── services.yml
│ │ │ ├── translations
│ │ │ └── views
│ │ ├── Tests
│ │ │ ├── Command
│ │ │ │ └── __CommandTestCases
│ │ │ ├── Controller
│ │ │ │ └── __ControllerTestCases
│ │ │ ├── DependencyInjection
│ │ │ │ └── __DependencyInjectionTestCases
│ │ │ ├── Form
│ │ │ │ └── __FormTestCases
│ │ │ └── Page
│ │ │ └── __PageTestCases
│ │ └── __DomainNameAppBundle.php
│ ├── Component
│ ├── Domain
│ │ ├── Shared
│ │ │ └── Value
│ │ │ ├── Gender.php
│ │ │ └── Interval.php
│ │ └── __DomainNameBundle
│ │ ├── Application
│ │ │ └── __ApplicationService.php
│ │ ├── Entity
│ │ │ ├── Model
│ │ │ │ └── __EntityDataModel.php
│ │ │ └── __EntityExtendsDataModel.php
│ │ ├── Event
│ │ │ └── __DomainEvent.php
│ │ ├── Exception
│ │ │ └── __DomainException.php
│ │ ├── Interface
│ │ │ ├── Dto
│ │ │ │ └── __ServiceDto.php
│ │ │ └── Facade
│ │ │ └── __ServiceFacade.php
│ │ ├── Listener
│ │ │ ├── __DomainEventListener.php
│ │ │ └── __EntityListener.php
│ │ ├── Repository
│ │ │ └── __EntityRepositoryInterface.php
│ │ ├── Resources
│ │ │ └── config
│ │ │ ├── doctrine
│ │ │ │ ├── __Document.mongodb.xml
│ │ │ │ └── __Entity.orm.xml
│ │ │ ├── services.yml
│ │ │ └── validation.yml
│ │ ├── Service
│ │ │ └── __DomainService.php
│ │ ├── Specification
│ │ │ └── __DomainSpecification.php
│ │ ├── Tests
│ │ │ ├── Application
│ │ │ │ └── __ApplicationServiceTestCases
│ │ │ ├── Entity
│ │ │ │ └── __EntityTestCases
│ │ │ ├── Event
│ │ │ │ └── __EventTestCases
│ │ │ ├── Exception
│ │ │ │ └── __ExceptionTestCases
│ │ │ ├── Interface
│ │ │ │ └── __InterfaceTestCases
│ │ │ ├── Listener
│ │ │ │ └── __ListenerTestCaess
│ │ │ ├── Service
│ │ │ │ └── __ServiceTestCases
│ │ │ ├── Specification
│ │ │ │ └── __SpecificationTestCases
│ │ │ └── Validator
│ │ │ └── __ValidatorTestCases
│ │ ├── Validator
│ │ │ └── __DomainValidator.php
│ │ └── __DomainNameBundle.php
│ └── Infrastructure
│ └── __DomainNameBundle
│ ├── Messaging
│ │ ├── __EmailConsumer.php
│ │ └── __LoggingConsumer.php
│ ├── Repository
│ │ ├── __DocumentRepository.php
│ │ └── __EntityRepository.php
│ ├── Tests
│ │ ├── Messaging
│ │ │ └── __MessagingTestCases
│ │ └── Repository
│ │ └── __RepositoryTestCases
│ └── __DomainNameBundle.php
├── tests
│ └── SeleniumTestSuite
├── vendor
└── web
src/main/java
├── UserInterface
│ └── ... (java files)
├── Application
│ ├── OneUseCase.java
│ ├── AnotherUseCase.java
│ └── YetAnotherUseCase.java
├── Domain
│ ├── SubDomain1
│ │ └── ... (java files)
│ ├── SubDomain2
│ │ └── ... (java files)
│ ├── SubDomain3
│ │ └── ... (java files)
│ └── SubDomain3
│ └── ... (java files)
└── Infrastructure
├── database
│ └── ... (java files)
├── logging
│ └── ... (java files)
└── httpclient
└── ... (java files)
src
└── Acme
├── ApiBundle
│ ├── Controller
│ │ └── UserController.php
│ ├── Resources
│ │ ├── config
│ │ │ ├── routing.yml
│ │ │ └── serializer
│ │ │ ├── User.User.yml
│ │ │ └── User.UserId.yml
│ │ └── views
│ │ └── User
│ │ └── all.html.twig
│ └── AcmeApiBundle.php
├── CoreDomain
│ └── User
│ ├── User.php
│ ├── UserId.php
│ └── UserRepository.php
└── CoreDomainBundle
├── DependencyInjection
│ └── AcmeCoreDomainExtension.php
├── Repository
│ └── InMemoryUserRepository.php
├── Resources
│ └── config
│ └── repositories.xml
└── AcmeCoreDomainBundle.php
application
user
UserController.php
UserService.php
blog
BlogController.php
BlogService.php
domain
user
Avatar.php
Address.php
UserService.php
UserRepository
blog
Post.php
Comment.php
BlogService.php
BlogRepository.php
User.php
Blog.php
infrastructure
user
UserService.php
blog
BlogService.php
app/
----ToDo/
--------App/
------------Providers/
----------------ToDoServiceProvider.php
----------------ConfigServiceProvider.php
------------Validators/
----------------LaravelValidator.php
------------ValueObject.php
------------BaseModel.php
--------Domain/
------------List/
----------------EloquentList.php
----------------ListRepository.php
----------------ListService.php
----------------ListValidator.php
----------------Priority.php
------------Task/
----------------EloquentTask.php
----------------TaskRepository.php
----------------TaskService.php
----------------TaskValidator.php
--------Http/
------------Lists/
----------------ListController.php
----------------ListPresenter.php
----------------ListViewComposer.php
------------Tasks/
----------------TaskController.php
--------Infrastructure/
------------Lists/
----------------ListRepositoryCacheDecorator.php
----------------EloquentListRepository.php
------------EloquentTaskRepository.php
com.some.namespace
application
services = (app services that talk to repositories and domain model)
validators = (validators for DTOs in app service layer)
domain
events = (domain events)
exceptions = (exceptions in domain - eg. during validation or business logic)
factories = (used to construct domain model objects - eg. construct from DTOs)
model = (full domain model with all entities, value objects etc.)
repositories = (interfaces only - implemented in infrastruct.)
services = (domain service interfaces only - implemented in infrastr.)
infrastructure
messaging = (message listeners - eg RabbitMQ - talks to app services)
repositories = (repository implementations)
sql = (one version of repository implementation)
rest = (rest endpoints - talks to application services)
services = (domain service implementations)
[projectName]-domain
└src
└main
├java
│ └com
│ └example
│ └domain ...(1)
│ ├model
│ │ ├Xxx.java
│ │ ├Yyy.java
│ │ └Zzz.java
│ ├repository ...(2)
│ │ ├xxx
│ │ │ └XxxRepository.java
│ │ ├yyy
│ │ │ └YyyRepository.java
│ │ └zzz
│ │ ├ZzzRepository.java
│ │ └ZzzRepositoryImpl.java
│ └service ...(3)
│ ├aaa
│ │ ├AaaService.java
│ │ └AaaServiceImpl.java
│ └bbb
│ ├BbbService.java
│ └BbbServiceImpl.java
└resources
└META-INF
└spring
├[projectname]-domain.xml ...(4)
└[projectname]-infra.xml ...(5)
(1) ドメインオブジェクトを格納する。 (2) リポジトリを格納する。エンティティごとにパッケージを作成する。 関連するエンティティがあれば、主となるエンティティのパッケージに、従となるエンティティのRepositoryも配置する。 (OrderとOrderLineなど)。DTOが必要な場合は、このパッケージに配置する。 RepositoryImplは、インフラストラクチャ層に属するが、通常、このプロジェクトに含めても問題ない。 異なるデータストアを使うなど、複数の永続化先があり、実装を隠蔽したい場合は、別プロジェクト(またはパッケージ)に、RepositoryImplを実装するようにする。 (3) サービスを格納する。業務(またはエンティティ)ごとに、パッケージインタフェースと実装を、同じ階層に配置する。 入出力クラスが必要な場合は、このパッケージに配置する。 (4) ドメイン層に関するBean定義を行う。 (5) インフラストラクチャ層に関するBean定義を行う。
[projectName]-web
└src
└main
├java
│ └com
│ └example
│ └app ...(1)
│ ├abc
│ │ ├AbcController.java
│ │ ├AbcForm.java
│ │ └AbcHelper.java
│ └def
│ ├DefController.java
│ ├DefForm.java
│ └DefOutput.java
├resources
│ ├META-INF
│ │ └spring
│ │ ├applicationContext.xml ...(2)
│ │ ├application.properties ...(3)
│ │ ├spring-mvc.xml ...(4)
│ │ └spring-security.xml ...(5)
│ └i18n
│ └application-messages.properties ...(6)
└webapp
└WEB-INF
├views ...(7)
│ ├abc
│ │ ├list.jsp
│ │ └createForm.jsp
│ └def
│ ├list.jsp
│ └createForm.jsp
└web.xml
(1) アプリケーション層の構成要素を格納するパッケージ。 (2) アプリケーション全体に関するBean定義を行う。 (3) アプリケーションで使用するプロパティを定義する。 (4) SpringMVCの設定を行うBean定義を行う。 (5) SpringSecurityの設定を行うBean定義を行う。 (6) 画面表示用のメッセージ(国際化対応)定義を行う。 (7) View(jsp)を格納する。
[projectName]-env
└src
└main
└resources
└META-INF
└spring
├[projectname]-env.xml ...(1)
└[projectname]-infra.properties ...(2)
(1) 環境に依存するBean定義(DataSource等)を行う。 (2) 環境に依存するプロパティを定義する。
参考:
【DDD入門】TypeScript × ドメイン駆動設計ハンズオン
https://zenn.dev/yamachan0625/books/ddd-hands-on/viewer/chapter11_domain_service