4

私は現在、.net と nhibernate を使用して、旅行業界のクライアント向けにかなり大規模なアプリケーションをいくつか開発しており、DDD の実装に関するいくつかの問題と、進行する最善の方法についてチーム内での意見の相違に直面しています。誰かが何らかのガイダンスを提供できることを願っています。

現時点では、集約ルート ([EntityName]Service) ごとにサービスを使用して、ドメインの外部にサービス レイヤーを実装しています。他のすべてのレイヤーは、これらのサービスを使用して、GetByThis() や GetByTheOther() などのメソッドを介して集約ルートへの参照を取得します。他のレイヤーからドメインへのすべての呼び出しは、これらのサービスを介して行われます。

サービスは、リポジトリへの挿入された参照 (他の場所では参照されない) を保持し、すべての保存/更新動作とトランザクションの管理も担当します。サービスメソッドは複雑さを増しており、条件付き作成ロジックのように、ドメインに属しているように見える動作をすることがあります (プロパティ = this の場合は子オブジェクトを何かに設定し、そうでない場合は別のものに設定します)。私たちのドメイン エンティティには、ほとんどが GetByThis() や HasAThing() などの単純なメソッドがあります。ドメインの表現力が失われているように感じます。

私の主な問題は次のとおりです。

  • サービス層にそれほど多くのロジックを含める必要がありますか? そうでない場合、どこに行くべきですか?ドメインの場合、集約ルートはリポジトリへの参照を保持する必要がありますか? はいの場合、これらはどのように注入されますか (集約ルートを作成するファクトリに?)
  • トランザクション性をどのように処理する必要がありますか?
  • エンティティ (または集約ルート) はドメイン サービスへの参照を保持する必要がありますか? もしそうなら、彼らはどのように参照を取得する必要がありますか?
  • エンティティの新しい ID を取得するには、リポジトリにラップしたストアド プロシージャを呼び出す必要があります。これをどこで参照しますか?多くの子エンティティを作成する必要があるエンティティのいくつかの複雑なメソッドは、これを参照する必要がありますか?

編集

@david-masters と @guillaume31 のよく考えられた回答に感謝します。

あなたは、私が感じていた「臭いコード」の感覚を解決するのを助けてくれました。

まず、対処すべき (非常に) レガシーな Oracle DB があると言うべきだったので、(他の問題の中でも) ID 生成の要件があります。

これを見た他の人にとっては、どちらの回答も優れたアドバイスを与えてくれましたが、私にとっては、これが最良のアドバイスでした。

「実用的な観点から、私が自問することは次のとおりです。ドメイン層の一部を取得して別のアプリケーションで再利用したい場合、その新しいアプリケーションでドメインを活用するために必要なすべてのビジネス ルールと動作が含まれているでしょうか。 ? そうでない場合は、現在アプリケーション側にある一部の部分をドメイン層に移動する必要があることを意味している可能性があります。」</p>

これを念頭に置いてドメインとサービス層を再評価し、設計上の問題を解決したと確信しています。

4

2 に答える 2

12

アプリケーション サービス レイヤーには、ドメイン ロジックを含めないでください。アプリケーション サービスの目的は、「オーケストレーション」です。ドメインの決定を行うべきではありません。すべてのビジネス上の決定は、ドメイン オブジェクトまたはドメイン サービスで行う必要があります。アプリケーション サービスは、コンシューマー (通常は UI) からの呼び出しを受け取り、ドメインおよびインフラストラクチャ サービスのメソッドを呼び出します。あなたが説明したように、アプリケーションサービスにはクラッド名を付けるべきではありません。ユースケースを説明する意味のある動詞が必要です。バンキング アプリケーションのアプリケーション サービスがどのようなものかの例を次に示します。

public class AccountService : IAccountService
{
    //These are injected via dependency injection on the constructor
    private readonly IAccountRepository _accountRespository;
    private readonly IEmailNotificationService _emailNotificationServce;

    public void FreezeAccount(Guid accountId)
    {
        Account account = _accountRespository.GetById(accountId);

        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
             account.Freeze();
             _accountRespository.Save(account);  
             _emailNotificationServce.Send(CreateFreezeNotification(account));           
        }
    }
}

依存関係がまったくないように、エンティティ/集計がリポジトリへの参照を保持しないことをお勧めします。集約が決定を下すために 2 番目の集約からの情報が必要な場合、アプリケーション サービスはそのリポジトリから 2 番目の集約を取得し、メソッドを介して最初の集約に渡す必要があります。

同じ原則をドメイン サービスにも適用します。ドメイン サービスが必要な場合 (通常、ユース ケースで 1 つのトランザクションに複数の集計を含める必要がある場合 (ただし、集計をより適切に設計して同時実行性の問題を減らすために、これを回避する必要があります))、アプリケーション サービスは最初に必要な集計をフェッチする必要があります。次に、それらをドメイン サービスに渡します。その後、ドメイン サービスは集約でドメイン ロジックを呼び出すことができます。

トランザクションは、このアプリケーション サービス レベルで処理する必要があります。上記のように、呼び出されて永続化されるすべてのロジックは、UnitOfWork にラップされます。このブロックがエラーなしで完了した場合にのみ、トランザクションが完了します。

ID に関して: 私は常にデータベース ID ではなく Guid を選択します。人生がずっと楽になり、あなたが説明する問題を回避できることがわかりました。データベースで ID (INT IDENTITY 列など) を管理する必要がある場合は、それをセカンダリ ID プロパティにして、ドメインの目的で Guid ID を使用してオーバーヘッドを節約できますか?

于 2012-06-28T10:20:30.213 に答える
5

サービス層にそれほど多くのロジックを含める必要がありますか? そうでない場合、どこに行くべきですか?

ここでは、ドメイン層ではなく、アプリケーション層のサービスについて話していると思います。あなたのドメインオブジェクトはほとんど貧血のようで、これはアンチパターンであると考える人もいますが、それについては多くの議論があります.

実用的な観点から、私が自問することは次のとおりです。ドメイン層の一部を取得して別のアプリケーションで再利用したい場合、その新しいアプリケーションでドメインを活用するために必要なすべてのビジネス ルールと動作が含まれているでしょうか? そうでない場合は、現在アプリケーション側にある一部の部分をドメイン層に移動する必要があることを意味している可能性があります。

ここでは、アプリケーション固有のルールではなく、純粋にドメイン ビジネス ルールについて話していることに注意してください。たとえば、一部の操作は 4 つの手順を含むウィザードを使用して実行する必要がある、最後の手順の最後にユーザーに確認を求める、最後の手順が適用された後にすべての変更が永続ストアにフラッシュされる、などです。 -ドメイン ルールではなく、特定のビジネス ルール。したがって、それらをドメイン層に移動するべきではありません。

ドメインの場合、集約ルートはリポジトリへの参照を保持する必要がありますか?

IMO集約ルートは、独自のリポジトリへの参照を保持し、それ自体を格納する方法を知っているべきではありません。これは、永続性の無視を破り、それを混乱させるドメインオブジェクトに追加の責任を導入するためです。ただし、集約ルートは、別のエンティティのリポジトリへの参照を保持する場合があります。

トランザクション性をどのように処理する必要がありますか?

トランザクションには 2 つのタイプがあると思います。

  • アプリケーション層の「ユーザー」トランザクション、別名「作業単位」。これらは、ユース ケースにまたがる、または Web アプリケーション内の Web ページの存続期間にわたって続く高レベルのトランザクションです (「ビューでセッションを開く」)。ORM フレームワークは、多くの場合、これらのトランザクションを管理するための機能を提供します。

  • ドメイン層でのトランザクション。これらは、ドメイン オブジェクトまたはサービスによって開始できます。例: FundsTransferService.Transfer() はトランザクションを内部的に使用できます。ここでは、プラットフォームの基本的なトランザクション処理を使用できます。

エンティティ (または集約ルート) はドメイン サービスへの参照を保持する必要がありますか? もしそうなら、彼らはどのように参照を取得する必要がありますか?

はい、エンティティはドメイン サービスを呼び出すことができます。ドメイン サービスには、どのエンティティにも属さないドメイン ルールと動作が含まれています。必要な分離のレベルに応じて、これらの依存関係をハードコーディングするか、エンティティに挿入することができます。

エンティティの新しい ID を取得するには、リポジトリにラップしたストアド プロシージャを呼び出す必要があります。これをどこで参照しますか?多くの子エンティティを作成する必要があるエンティティのいくつかの複雑なメソッドは、これを参照する必要がありますか?

エンティティの ID 生成をオンデマンドでアクセスできるようにすることはお勧めしません。David が指摘したように、エンティティを新たに作成するときに、言語レベルで Guid を生成する方がよい場合がよくあります。

それでも ID 生成ルートに進みたい場合、ID ジェネレーターの呼び出しは通常、エンティティ自体ではなく、エンティティの Factory の仕事です。

于 2012-06-28T12:59:54.663 に答える