Rのつく財団入り口

ITエンジニア関連の様々な話題を書いているはずのブログです。

【感想】『ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本』:挫折せずDDD入門できる本【後編】

DDDに再入門しよう

同書の読書記録&感想の後編です。

Chapter 10 データの整合性を保つ

10.1 整合性とは

  • 矛盾がなく一貫性のあること。

10.2 致命的な不具合を確認する

  • UserApplicationService#Register() のこれまでのサンプルコードは登録前にユーザ重複チェックがあるが、複数ユーザから同時に処理すると登録できてしまう。

10.3 ユニークキー制約による防衛

  • ユニークキー制約をつければ、コード中の重複チェックはいらなくなる…と考えるのは危険。コードから読み取れなくなる。またRDBのユニークキー制約という特定の技術基盤に依存してしまう。
  • コードでの重複チェックとユニークキー制約、両方使うとよい。

10.4 トランザクションによる防衛

  • UserRepositoryはメンバ変数でSqlConnection型を持ち、コンストラクタ内でセット。Save()などのメソッド引数にSqlTransaction型を追加。SQL文実行の際にこのconnectiontransactionを使う。
  • 呼び出し元の外側、UserApplicationServiceもメンバ変数でSqlConnection型を持ち、コンストラクタ内でセット。処理のメソッドで以下のようにする。
public void Register(UserRegisterComamnd command)
{
    using(var transaciton = connection.BeginTransaction())
    {
        // ユーザー生成処理、重複チェックの後に...
        UserRepository.save(user, transaction); // DBへの保存処理の引数にトランザクションを渡す
        transaction.Commit(); //ここでトランザクションをコミット
    }
}
  • 上記のトランザクション活用で整合性は担保できるが、RDBの技術基盤に依存してしまう。
  • C#には、ここは整合性が必要な処理ですよ、と明示的に主張できる「トランザクションスコープ」という機能がある。RDBという特定の技術基盤に依存しない。
public void Register(UserRegisterComamnd command)
{
    using(var transaciton = new TransactionScope())
    {
        // UserRepositoryを使って
        // ユーザー生成処理、重複チェック...
        // DBへの保存処理をしてから...
        transaction.Complete();
    }
}
  • Javaならアスペクト指向プログラミング(AOP: Aspect Oriented Programming)のアプローチで同じようなことが実現できる。
@Transactional
public void Register(UserRegisterCommand command) {
    // ユーザー生成処理、重複チェック、
    // UserRepositoryを使ってDBへの保存処理のコード
    // コード本体を書かなくてもメソッド正常終了でコミット、
    // 途中の例外でロールバックを自動でやってくれる
}
  • ほかに、オブジェクトの変更を記録するオブジェクト「ユニットオブワーク」を使うパターンもある。
  • UnitOfWorkクラスがメソッドで public void RegisterNew(object value) などを持つ
  • エンティティ用の抽象クラスEntityMarkNew(), MarkClean(), MarkDirty(), MarkDelete() などのメソッドを持ち、中はUnitOfWork.Current.RegisterNew()...などを呼ぶだけ。
  • このEntityクラスを継承したUserクラスなどは、保存時にMarkNew()などを呼ぶ。
  • 呼び出し元の外側のUserApplicationServiceもメンバ変数でUnitOfWorkを持つ。Register()などの最後にUnitOfWorkインスタンスCommit()を呼ぶ。
  • このUnitOfWorkクラスが中にメンバでUserRepositoryを持つパターンもある。
  • C#というか.NET Core, ADO.NETがデフォルトで持っているデータアクセスの仕組み、Entity Frameworkはこのユニットオブワークの実装である。
  • 作者のnaruseさんはC#ならトランザクションスコープ、それ以外はAOPを使うとのこと。
  • また、トランザクションが起こすロックはなるべく小さくすること。一度のトランザクションで保存するオブジェクトは少なく、小さいほうがよい。

UnitOfWorkパターンは明確には初めて知りました。なかなか変わったやり方に見えますがこういう解決方法もあるのですねえ。この章はあまりDDDとは関係なく一般的なトランザクションの話でした。

ja.wikipedia.org

f:id:iwasiman:20210731144132p:plain
ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本

Chapter 11 アプリケーションを1から組み立てる

11.1 アプリケーションを組み立てるフロー

  • 機能追加を例にDDDのパターンを再確認する章。
  • まず機能を定め、必要なユースケースを洗い出し、必要なルールと概念からドメインオブジェクトを準備。そしてアプリケーションサービスを作る。

11.2 題材とする機能

  • ユーザー機能を作ってきたので、複数ユーザーが入れるサークル機能を作る。サークルの作成、サークルへの参加が可能。

11.3 サークルの知識やルールをオブジェクトとして準備する

  • サークルはライフサイクルがあるので、エンティティになる。
  • 識別子としてCircleIdクラスを作成。Valueを持つだけ。
  • CircleNameもクラスとして作成。Valueを持つだけ。コンストラクタでサークル名制限のルールを記述。Equals()メソッドを持つので、C#特有のIEquatabke<CircleName>インタフェース実装で作成。
  • Circleクラスを作成。メンバにCircleId, CircleName, User型のowner, List<User>型のmembers
  • 永続化用にICircleRepositoryを用意。メソッドはSave, Find
  • Circleクラス生成用にファクトリとしてICircleFactoryを用意。メソッドはCircle Create(CircleName name, User owner)
  • 重複確認はドメインサービスの仕事としてCircleServiceクラスを作成。メンバにリポジトリを持ち、public bool Exists(Circle circle)メソッドを用意。

11.4 ユースケースを組み立てる

  • サークル作成コマンドはCircleCreateCommandとして、中はuserIdnameを持つだけ。
  • 実際のサークル作成はCircleApplicationServiceクラスに。メンバでファクトリ、CircleUserリポジトリドメインサービスの4つを持つ。
  • public void Create(CircleCreateCommand command) メソッドの中でトランザクション生成、既存Userと既存Circleの存在確認をして、リポジトリSaveして作成し、トランザクション終了。

  • サークル参加コマンドはCircleJoinCommandとして、中はuserIdcircleIdを持つだけ。

  • 作成済みのCircleApplicationServiceクラスに public void Join(CircleJoinCommand command) メソッドを実装。トランザクション生成、UserCircleの存在確認、人数確認、circle.Members.add(menber)でエンティティにそのユーザを追加して、circleRepository.Save(circle)で保存、トランザクション終了。
  • ここでサークルのメンバーは最大30人という仕様、ドメインのルールががある。オーナーが1人で残りが29人なので、コード内では >= 29で表現。ほかは >= 30 だったりわかりにくい。
  • 例えばメンバー勧誘のユースケースが追加されたら、public void Invite(CircleInviteCommand command)メソッド追加。招待されるUserと招待するUserの存在確認、現在メンバーが>= 29だったら中止、その後CircleInvitationオブジェクトを作り、リポジトリSave
  • この数字の29や30がコード内のあちこちに散在しそうである。→次章の「集約」の考え方。

これは機能追加としてわかりやすい例ですね。DDDにちゃんと従うとクラス数がだいぶ増えますが、とても綺麗な実装になって理解しやすいです。
>= 29 の話は定数としてどこかに定義するのか、それとも判定のロジックをメソッド化して一箇所に定義するのかな、ドメインルールだからドメインサービス内だろうか?などと思いながら次へ...

Chapter 12 ドメインのルールを守る「集約」

12.1 集約とは

  • データの変更単位となるオブジェクトをひとまとめにしたグループ。不変条件(厳密にはある処理の間、その真理値が真のままであること)を単位として切り出される。
  • 集約に何が含まれるかの「境界」、外部からの操作を常に受け持つ集約の「ルート」であるオブジェクトを持つ。(AR: Aggregate Root)
  • 例えばUserの集約はAggregate RootがUser、内部にUserIdUserName。この3つを入れた丸い線が境界。ユーザ名を変更しようとしたら常にUserクラスのChangeName()メソッド経由となる。
  • サークルの集約も同じでAggregate RootがCircle、内部にCircleIdCircleName。この3つを入れた丸い線が境界。別の集約であるUser0...nで線でつながる。たとえばサークルにユーザーを追加しようとしたら、circle.Members.Add(user)のようにメンバ変数に直接アクセスはNG、circle.Join(user)のようにメソッド経由が正しい。
  • このように内部データにはアクセスせずそれを保持しているオブジェクトにメソッド経由で依頼する形にすると、より直感的、よりオブジェクト指向的になる。
    デメテルの法則」(Law of Demeter)、最小知識の法則。直接の友達とだけ話す。オブジェクトをまたいでまた別のオブジェクトを呼ばない。
  • なお実装的には内部で<List>Users members; で持っていても、集約の図ではこれは書かない。

  • デメテルの法則に従うと、メンバー追加時の1サークル内上限は30人のルールを表すロジックは内部データに直接触れるのはNG。Circleクラスにチェックするふるまいを追加、外部からはこのメソッド経由で依頼するのが正しい。

public class Circle
{
    private List<Users> members;
    public bool IsFull()
    {
        return members.Count >= 29;
    }
}
  • 上のやり方を徹底すればコード内のロジック散在も防げる。より柔軟なコード、メンテナンス性の向上が見込める。
  • 内部データにgetterで触らないように徹底するには、ルールで定めた紳士協定とするやり方もある。
  • 強制するには下の「通知オブジェクト」を使う方法もある。欠点はクラス数が増えること。
public class SomeRepository: IUserRepository
{
    public void Save(User user)
    {
        // IUserNotificationを実装した通知Objで、メンバにUserIdとUserNameを持つ
        var notifyObj = new UserDataModelBuilder();
        // Userクラス内のメソッドで、引数のObjにUserIdとUserNameを渡す。
        // 中で完結するのでgetterで外から触らずデメテルの法則キープ!
        user.Notify(notifyObj); 
        // 渡されたUserIdとUserNameを元に、Objが別のデータモデルクラスを生成。
        var userDataModel = notifyObj.Build();
        
        // このデータモデルクラスを使った別処理...
    }
}

12.2 集約をどう区切るか

  • リポジトリは変更の単位である集約ごとに用意する。
  • CicleList<User> members; を持っていてUserの変更もできてしまうと後からの修正が大きくなってしまう場合は、List<UserId> members; のように識別子だけ持つ方法もある。インスタンスがないのでそもそもUserの変更はできなくなる。メモリ節約にもつながる。

12.3 集約の大きさと操作の単位

  • 大きいトランザクション=大きいロック範囲 なので、集約はなるべく小さい方がよい。複数の制約を同一トランザクションで操作するのもなるべく避ける。
  • 避けられない場合は、クラウドでよく見られる「結果整合性」で行う方法がある。例えばユーザ名重複は大きい処理終了直後は担保されないが、1日1回のバッチ実行でしばらく経つと治って整合性が保たれる、など。

12.4 言葉との齟齬を消す

  • コードと言葉の齟齬はなるべくないほうがよい。1サークル内上限30人のルールはCircleクラスにこう定義するとなお良い。
public class Circle
{
    private List<Users> members;
    public bool IsFull()
    {
        // 別のメソッドも使う
        return CountMembers() >= 30;
    }
    public int CountMembers()
    {
        // 常に1名は管理者なのがこれでコードからも読み取れる...?
        return members.Count + 1;
    }
}
  • また集約の境界線を引いていく作業はドメインに寄りすぎてもシステムに寄りすぎてもダメで、バランスが大事。

本章のAggregate Rootとなる親のオブジェクトから下位のオブジェクトに線が繋がる集約の話は、マイクロサービスの文脈でも登場します。自分的には書籍『マイクロサービスパターン』によく出てきたのが思い出されます。
たとえばうまく境界線を定めてRestaurantがルートになるドメインができたら、マイクロサービスの単位もこのRestaurantがルートのレストランサービスになり、他のサービスからネットワークを介して変更の申請が来たらRestaurantが受け取って下位のオブジェクトも順々に更新していって...という具合ですね。
なるほどこう繋がるのかと納得でした。同書でも集約やトランザクションの単位はなるべく小さくと書いてあります。

iwasiman.hatenablog.com

デメテルの法則」は普段はあまり意識していないのですが、オブジェクト指向のやり方に沿ってクラスに定義したメソッドに処理を任せるようにしていけば、だいたい自然とこうなるかなあという感じ。
通知オブジェクトを使う方法は面白いですね。ただクラスも増えて手間も増えるので、そこまでしてgetter使うの禁止にして意味あるの...?楽しい...?という気もします。

ja.wikipedia.org

Chapter 13 複雑な条件を表現する「仕様」

13.1 仕様とは

  • オブジェクトの評価処理はアプリケーションサービスに記述されてしまうことが多いが、ドメインの重要なルールである。評価処理をクラスに切り出したものを「仕様」と呼ぶ。
  • 例えばサークルの上限判定、今まではCircle#IsFull()に実装。しかしプレミアムユーザの人数で上限が変わるという複雑な条件になったら?
    • アプリケーションサービスであるCircleApplicationServiceに書くとどこでも書けるので、ロジックが散在してしまう。
    • Cicleクラスに書くと、IsFull()メソッドの引数にIUserRepositoryが必要になる。リポジトリドメイン由来のものではないので、エンティティと値オブジェクトはリポジトリ操作を直接行うことは避けるべき。
  • ここでCircleFullSpecificationという仕様クラスを新規作成。メンバにIUserRepositoryを持ち、メソッド bool IsSatisfiedBy(Circle circle) で判定条件を書くだけ。
  • するとCircleApplicattionServiceではロジックの途中でこの仕様クラスを呼べば良い。メンバ変数では持たずメソッド内でnewして使う。

  • 別の話としてCicle内のユーザーをListでなくList<User>で持つCircleMembersクラスを専用に作るような、「ファーストオブジェクト」パターンもある。上記の仕様クラスCircleFullSpecificationもメソッド引数が IsSatisfiedBy(CircleMembers members)のように変わる。

13.2 仕様とリポジトリを組み合わせる

  • 例えば最近一ヶ月以内に作られた、あるいはメンバ数10人以上のサークルを優先するお勧めサークル検索機能を作るとする。
  • リポジトリCircleRepositoryにメソッド追加だと、ドメイン内のルールがインフラの領域にはみ出てしまいよくない。
  • 仕様クラスCiecleRecommendSpecificationを新設。アプリケーションサービスからはまずリポジトリを呼んで全サークル検索、1件づつこの仕様クラスの判定メソッドに渡せば、リポジトリから判定条件は消える。
  • リポジトリのDB検索メソッドにpublic List<Circle> Find(ISpecification<Circle> specification)のように仕様クラスを渡すパターンもある。

  • この仕様をリポジトリに渡すパターンのデメリットは、まずリポジトリがDB全件検索が必要な場合などの性能問題。DDD的に正解でも利用者に不便を強いるのはよくない。

  • こうした場合はドメインオブジェクトから離れる場合もある。1回のSQL文発行の中に条件を全部入れて解決、ORMを利用するなど。例ではリポジトリではなくCircleQueryServiceという別クラスで実現。
  • DBへの書き込み処理(コマンド)はドメインオブジェクトを積極利用、読み取り(クエリ)ではある程度ゆるくするやり方はコマンドクエリ分離の原則(Command-Query Separation:CQS)、コマンドクエリ責務分離の原則(Command-Query Responsibility Segregation:CQRS)という考え方もある。
  • 類似してC#Entity Framework では検索結果のコレクションに条件が適用されたときに初めて実際のDB検索が行われる、「遅延実行」が実装されていたりする。

CQRSの考え方は書籍『マイクロサービスパターン』でも紹介されていました。真面目に作ったら各マイクロサービスに散らばってしまうようなデータを、検索専用ビューを用意して各サービスで検索時に限ってはこちらを使う。この検索専用ビューの中のデータ更新は別タイミングで行われる...というものですね。

iwasiman.hatenablog.com

やはりドメインの設計原則の理想に従うだけでは現実的には上手くいかない場合もあるのだなあと思いました。

docs.microsoft.com

docs.microsoft.com

Chapter 14 アーキテクチャ

14.1 アーキテクチャの役目

  • アーキテクチャは知識を記述する箇所を示す方針。ドメインのルールが外に漏れ出すことを禁止し、一箇所にまとめることを促してくれる。
  • 何がどこに記述されるべきかという疑問に対する回答を示してくれるので、開発者は「ドメインを捉え、上手く表現する」ことに集中できる。ソフトウェア特有の事情からドメインオブジェクトを防衛できる。
  • DDDにとってアーキテクチャは主役ではないので、どのようなものでもよい。

14.2 アーキテクチャの解説

よく語られるものが3つ。

レイヤードアーキテクチャ

  • エヴァンス本でも言及されており、そこでのレイヤーは4つ。
  • プレゼンテーション層(ユーザーインターフェース層):UIとアプリケーションを結びつける層。WebやCLI
  • アプリケーション層:ドメイン層のものをまとめる層。ドメイン層の呼び出し元のクライアントになる。DDDではアプリケーションサービス。ドメイン層もインフラ層も呼ぶことがある
  • ドメイン層:ドメインオブジェクトはここに隔離し、他の層に流出させない。
  • インフラストラクチャ層:他の層を支える技術的基盤へのアクセス。永続化やメッセージ送信。
  • 依存は常に上から下、プレゼンテーション→アプリケーション→ドメイン→インフラ
  • エヴァンス本のシーケンス図ではユニットオブワークのパターンが使われており、昨今では珍しい。

実装例が以下のクラス群。

UserController:
プレゼンテーション層。メンバ変数にUserApplicationServiceを持つ。GETやPOSTでリクエストが飛んできたらCommndクラスを作って引数にしてUserApplicationServiceの各メソッドを呼ぶ。
UserApplicationService:
アプリケーション層。メンバ変数にUserFactory, UserRepository, UserServiceを持つ。Get, Register, Updateなどのメソッド内でドメイン層のドメインオブジェクトを作ったり、ドメインサービスのロジックを呼んだり、リポジトリから検索したり保存したり。メソッドの戻り値がUserRegisterResultなどResultクラスを使っている。
User:
ドメイン層のドメインオブジェクト。
UserService:
ドメイン層のドメインサービス。メンバ変数にUserRepositoryがいてロジック処理など。
EFUserRepository:
インフラ層。IUserRepositoryを実装、技術基盤と結びついて永続化処理。

ヘキサゴナルアーキテクチャ

  • 六角形の中心にApplication。中心と周囲とつなぐAdapterがあり、その外側にWebやDBがいる。
  • ゲーム機の例えが分かりやすい。中心にあるゲーム機本体からしたら繋がりさえすればコントローラーが複数種類あってもよいし、モニターも複数あってもよいし、データの保存先はハードディスクでもクラウドでも良い。
  • ポートアンドアダプターパターンとも呼ばれる。アプリケーションへの入力がプライマリポート/アダプター、アプリケーションが外部へ出力するのがセカンダリポート/アダプター。ポートが抽象で実装したオブジェクトがアダプタ。
  • これまで例に出てきたUserApplicationServiceクラスがこれを実現している。このクラスの呼び出しクライアントがプライマリアダプタ。Update()などのメソッドがプライマリポート。このクラスが呼び出すIUserRepositoryセカンダリポートになる。

クリーンアーキテクチャ

f:id:iwasiman:20200827210602j:plain
有名なThe Clean Architectureの図

  • 4つの同心円からなるアーキテクチャ。真ん中にあるEntitiesはDDDのエンティティとちょっと違い、ビジネスルールをカプセル化したオブジェクト。ドメインオブジェクトには近い。
  • UIやデータ永続化の仕組みは端に追いやって、依存は常に円の中心へ。ヘキサゴナルアーキテクチャと目指すところは同じ。
  • インターフェースを活用して依存の方向を制御する。

f:id:iwasiman:20200828160651j:plain
コーンな図

  • どのアーキテクチャでも人間が一度に多くのことを考えすぎないよう、方針を定めてくれる。考える範囲が狭まることでより深いモデルの考察ができる。ドメインの隔離を促すことができればDDD的にはどのようなアーキテクチャでも良い。

レイヤードアーキテクチャのアプリケーション層のサンプルコードのメソッド戻り値のResultクラスというのが前は出てこなかったような…? あとヘキサゴナルアーキテクチャのポートとアダプターの差がよくわかりませんでした。
 クリーンアーキテクチャは有名なので本で読んだ方も多いのではないでしょうか。当ブログにも感想記事があるのでこちらもどうぞ。
 DDDと用語や考え方がまた若干違います。アプリケーションのロジック部分のコードを中心にして、周囲の技術基盤に影響されないように、変更に強いコードにしていこうという根っこの考えは同じですね。

iwasiman.hatenablog.com

Chapter 15 ドメイン駆動設計のとびらを開こう

最後はまとめの章。

15.1 軽量DDDに陥らないために

  • パターンだけを取り入れる手法を軽量DDDと呼ぶ。DDDが目指すのはパターンに終始することではなくドメインの本質に向き合うことなので、これはよくない。

15.2 ドメインエキスパートとモデリングをする

15.3 ユビキタス言語

  • 混乱を避け翻訳の手間を避けるため、プロジェクトの共通言語をユビキタス言語と言う。ドメインエキスパート側の言葉ベースだが不正確だったら改良していく。
  • ユーザーの「名前を変更する」がそのドメインで適切な表現だったら、コード側のクラスメソッド名もUpdateNameでなくChangeNameでよい。

15.4 境界付けられたコンテキスト

  • 例えばこれまで例に上がってきたUserCircleクラスで、急遽システムにログイン用のパスワードが出てきたらどうするか。元のUserの活動にログインは関係ない。
  • こうした場合はC#ならこれまでのUserクラスはCore.Model.Users名前空間へ。認証用の別のUsersクラスを作ってAuthenticate.Model.Usersのような別の名前空間に配置するとよい。別のほうのUserクラスはメンバ変数にUserIdPasswordを持つ。こうした区別を「境界づけられたコンテキスト」と呼ぶ。

15.5 コンテキストマップ

  • コンテキスト同士の関係を定義し、ドメイン全体を俯瞰できるようにした図を作ると良い。
  • AuthenticateCoreの大きな丸があり、その中にUserCircleクラスがあって線で関連を繋いだりするもの。
  • 境界づけられたコンテキストがそのまま開発チームの境界になったら、互いに協力していく。

15.6 ボトムアップドメイン駆動設計

  • 作者さんの意見ではDDDはボトムアップのアプローチが良い。根底にあるものを学んで知識を積み上げていける。
  • さらなる高みへ登るなら、『エリック・エヴァンスのドメイン駆動設計』へ...

Appendix ソリューション構成

A.1 ソフトウェア開発の最初の一歩

Visual StudioC#で開発する場合の限定ですが、ソリューション構成の例が載っていて何気にとてもお役立ちです。他の言語でもパッケージ、フォルダ構成の参考になるでしょう。

ドメインレイヤーの構成例が以下。

Domain/ // 実際には境界づけられたコンテキストの名前になる
  Models/
    Circles/ 
      // ここに値オブジェクト、エンティティ、
      // リポジトリやファクトリのインターフェース、仕様クラスも
    Users/
      // 同様
  Services/
    // UserServiceのようなドメインサービス
  Shared/ 
   // ISpecification のような他のプロジェクトでも共用できるもの

ファクトリやリポジトリはクラスの種類によってフォルダ分割せず、ドメインオブジェクトと一緒のフォルダに置くのがポイント。

アプリケーションレイヤーが以下。

Application/
  Circles/
    // アプリケーションサービス、Commandクラス、Resultクラス、DTOなCircleDataなど
  Users/
    // 同様

こちらはアプリケーションサービスごとにフォルダ分け。クラスが大きくなってきたらクラスの種類によって細分化もあり。クリーンアーキテクチャ寄りにするとCircles/ の下がさらにCreate/, Get/ などに細分化。

インフラストラクチャのレイヤーが以下。

EF/ // 技術基盤にEntity Frameworkを使ったクラス群
  Circles/
    // リポジトリ、対象ドメインオブジェクトのFactory
  Users/
    // 同様
InMemory/ // テスト用にメモリ上に保存するだけのクラス群
  Circles/
    // 同様
  Users/
    // 同様

こちらは技術基盤ごとにフォルダ分け、一緒にしてもよいとのこと。

A.2 ソリューション構成

C#では.slnファイルを開くとVisual Studio上に展開されるひとつのソリューション(複数プロジェクトをまとめたようなもの)があり、その中に1~個のプロジェクトがあります。この構成について。

ドメイン/アプリケーション/インフラストラクチャーがすべてひとつのプロジェクト:
非推奨。

ドメイン/アプリケーション/インフラストラクチャーがすべて別のプロジェクト:
なおインフラはEFInfrastructure, InMemoryInfrastructure など技術基盤ごとにそれぞれ別プロジェクト。 ドメインオブジェジェクトのメソッドはpublicにする必要があり、呼ぶべきでない場所のクラスから呼べてしまう可能性が増える。

ドメイン/アプリケーションが同じプロジェクト、インフラストラクチャーだけ別:
こちらもインフラはEFInfrastructure, InMemoryInfrastructure など技術基盤ごとにそれぞれ別プロジェクト。 ドメインオブジェジェクトのメソッドはinternalにすれば、他のプロジェクトからは呼べなくなって安全。
このへんはプログラム言語の特性によって差異あり。なぜその構成にしたのか理由付けをはっきりしておく。

考え抜かれたソリューションには美しさが宿る...なんてあって、これは全体の構成を決める立場だと分かるなあというところ。

まとめ:挫折せずDDDに入門できる本

 ネットの書評も良い本書ですが、確かにボトムアップで丁寧に解説してくれていてありがたいです。長い本ですが章立て、中身も細分化されて個々の話は短いので割とすんなり読めます。
 読者に理解させる意図を徹底しているのか、例も簡単で分かりやすいです。サンプルコードのC#も例が簡単なので最初はUserクラスの話が延々と続くようにも見えますが、徐々に本格的になっていきます。
リポジトリでDBに接続する部分のコードなど、技術基盤に依存した一部を除くと特に難易度は高くなく、普段C#をお使いでない方でもオブジェクト指向言語がある程度分かる方なら雰囲気で理解できると思います。

 自分は完全なDDDを仕事のプロジェクトで実践している訳ではないし、別に導入しなくても変更に耐えうる設計で問題なくやれてるのよね〜というスタンスですが、それでも学ぶところのある本でした。DDDを徹底していなくても、ふだん自分が設計で気にかけているところなんかが予想した以上に共通しているなあというところ。これで...エヴァンス本も読める...かも!(※死亡フラグ)
 特にプログラミングがある程度できるようになってクラス設計で悩んだり、システム全体の設計やアーキテクチャが気になってきた頃、中級者ぐらいの方にはDDDを実践しているかに限らず役に立つと思います。

第14章メインでサンプルコードもあります。

github.com

作者のnrsさんのブログに後書き記事もあります。

nrslib.com

f:id:iwasiman:20210731144132p:plain
ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本

関連書籍

『現場で役立つシステム設計の原則』もDDDの考え方に寄っていることで有名な本。現場力が高まるのでこの本もお勧めです。当ブログにも感想記事があります。

iwasiman.hatenablog.com

原典の『エリック・エヴァンスのドメイン駆動設計』。

その続き的な位置づけの『実践ドメイン駆動設計』。

『「実践ドメイン駆動設計」から学ぶDDDの実装入門』という本もあります。

再掲ですが『Clean Architecture』もDDDと並んでよく話題に上がります。この本もお勧めです。当ブログにもまとめ&感想記事があります。

iwasiman.hatenablog.com