🙈

DDDを意識した際のpackage構成

2020/09/26に公開
3

概要

DDDを勉強した際に増田さんlittle_handsさんnrsさんのサイトをよく拝見させて頂いているのですが、実際に自分でDDDで取り入れた際にpackage構成をどうした方がいいかというのを考えることが多いので備忘録と考えの整理の為に残します

DDDに関しての自分のメモを元に書いています

アーキテクチャ

isolating-the-domainのアーキテクチャが個人的にはしっくりきています

外部との接地面はPresentation層とInfrastructure層のみでApplication層やDomain層は自分たちの関心毎に集中しやすくなっています

package構成

root
├── application
│   ├── service(ApplicationService)
│   └── sharedservice(サービス間で使いたいサービス)
├── domain
│   ├── model(値オブジェクトなどを格納したオブジェクト)
│   ├── repository(インターフェース)
│   ├── service(DomainService)
│   └── value(値オブジェクトや区分オブジェクト)
├── infrastructure(repositoryの実態)
│   ├── composite(複数のdatasourceやexternalapiの結果を結合したりする場合に使用)
│   ├── datasource(DBなど)
│   ├── externalapi(外部APIなど)
│   └── transfer(外部ストレージなど)
└── presentation
    ├── controller
    ├── secutiry(認証関連やCSRFトークンなどの処理)
    └── interceptor(コントローラー共通処理)

Application層のServiceとDomain層のServiceの違い

ApplicationService:ユースケース(プロジェクトの作成、参照、更新、削除)
DomainService:ドメインオブジェクトに関する操作だがドメインオブジェクト内にはおけない物(重複確認など)

DomainServiceを使う機会は滅多にないです

ボトムアップドメイン駆動設計での説明がわかりやすかったです

ドメインオブジェクトの定義

model

値オブジェクトなどを格納したオブジェクト(Entity)

value

値オブジェクトや区分オブジェクト(ValueObject)

collection

modelやvalueなどを束ねたオブジェクト(Listなどのwrapper
置き場所は元のオブジェクトと同階層に置くのでmodelだったりvalueだったりする
名称は元のオブジェクトの複数形にする(またはsuffixにCollectionとつけるなど

参考:DDD基礎解説:Entity、ValueObjectってなんなんだ

外部オブジェクトとの変換はDTOに任せる

DBから取得した値を直接modelやvalueに変換するとやりづらいことが多かった
変換用のDTOを用意してドメインオブジェクトへの変換を任せることで特に外部APIのレスポンスの形式などを意識しなくていい

各DTOの名称は下記のように用途毎に分ける

リクエスト関連:Form(Web)、Request(API)
レスポンス関連(JSON):Response、View
DBや外部API:Entity

APIの場合はOASによるgeneratorを活用すればリクエスト関連、レスポンス関連のオブジェクトは自動生成することができる

generatorでオブジェクトを作成した場合にはControllerと同階層にMapperを用意し、ドメインオブジェクトへの変換を行うようにする

packageをどこまで切るべきか

Application層のserviceやsharedservice、Domain層のrepositoryやserviceに関してはpackage直下にファイルを作成してしまうで良さそう
上記以外の物に関しては用途毎のpackageを切った方がいい

例えばdatasourceなどの場合だと実際にアクセスを行うMapperやDTO、Mapperを呼び出すクラス(repositoryのインターフェースを継承)などを置くことになる為、packageを切らないとごちゃごちゃしてしまう
Serviceなどに関してはServiceからしか呼ばないものなどはないため、packageを切る必要性が低い

Spring BootとMybatisを利用する場合には下記のような設定を入れることでXMLファイルに関しても同package内で管理することができ便利である(サンプルはGradleを使用

sourceSets {
    main {
        // mybatis SQL map XMLファイルをjava以下にも設置できるようにする
        resources.srcDir 'src/main/java'
    }
}

雑感

package構成やドメインオブジェクトなどは運用をしていく上で手を加えていくべきものである為、まだ変更のしやすさを実感できているわけではないので今後どうなるかは気になるところです

運用等をしていった結果変わった点などはまた残していけたらと思います

Discussion

mm1995tkmm1995tk

パッケージ構成に非常に悩んでいたので大変参考になりました!
ひとつ質問なのですがDTOをdatasourceに置くとアプリケーション層がインフラ層に依存することになりませんか?

依存の方向を守るためにはアプリケーション層に配置するのがいいような気がするのですが、違和感があるので今はroot直下にDTOを置いています。何かアドバイスいただけると大変助かります。

も

コメントありがとうございます!

外部オブジェクトとの変換はdtoに任せるに書かせて頂いていますがInfrastructure層から返す際にはDomain層のドメインオブジェクトに変換を行ってから返すのでApplication層が依存するのはDomain層になります

イメージとしては下記のように一度Entityにマッピングしてからドメインオブジェクトに変換するとSQL側でドメインオブジェクトに合わせたりすることをしなくていいので使いやすく感じています
MyBatisを使う場合ですとresultMapとかTypeHandlerを駆使すれば直接ドメインオブジェクトにマッピングすることもできますが個人的にはXMLファイル側で頑張るよりはEntityに格納しちゃう方が楽に感じています

package jp.moriopg.demo.infrastructure.datasource.user;

interface UserMapper {

    UserEntity select(UserId userid);

}
package jp.moriopg.demo.infrastructure.datasource.user;

@RequiredArgsConstructor
public class UserDataSource implements UserRepository {

    final UserMapper userMapper;

    @Override
    public User findUser(UserId userId) {
        return userMapper.select(userId).toUser();
    }

}
package jp.moriopg.demo.infrastructure.datasource.user;

@RequiredArgsConstructor
class UserEntity {

    final String name;

    public User toUser() {
        return User.create(name);
    }

}
mm1995tkmm1995tk

見落としていました。
詳しくありがとうございます!