5

DDDを実装しようとしていますが、コツをつかんでいるように感じますが、いくつか問題もあります。

ドメインオブジェクトの90%で、最後に変更を加えたユーザーを知りたいです。私は完全な監査証跡を必要としません-それは私のニーズにはやり過ぎです。

私のすべてのクラスは、以下を含む抽象基本クラスを実装しています。

    public abstract class Base
    {
       public User LastChangedBy { get; set; }
       public DateTime LastChangedDate { get; set; }
    }

オプション1:DDDの原則に従いますが、それほどエレガントではありません

オブジェクトを無効な状態にしないでください

public abstract class Base
{
   public User LastChangedBy { get; protected set; }
   public DateTime LastChangedDate { get; protected set; }
}

public SomeObject
{
   .....
   SomeBehaviorThatChangesObject(User changedBy, ...)
   AnotherBehaviorThatChangesObject(User changedBy, ...)
}

私はすべてのセッターをプライベートにする必要があります。基本クラスのセッターは保護されています。オブジェクトに加えられたすべての変更は、(ユーザーchangedBy)をパラメーターとして受け取るメソッドを介して行う必要があります。

非常に安全ですが、ユーザーがオブジェクトに6つの変更を加える可能性があるため、それぞれにUserオブジェクトを指定する必要があります。まあ、実際には、ドメインモデルのほぼすべてのメソッドにこれを提供する必要があります...

オプション2:最良のものではありませんが、私はそれについて読みました

boolIsValidフィールドを導入します。すべてのセッターで、IsValidをfalseに設定しました。このフィールドをtrueに設定するメソッドAcceptChanges(UserchangesAcceptedBy)を作成しました。オブジェクトを永続化する前に、オブジェクトが有効かどうかを常に確認することを忘れないでください。

public abstract class Base
    {
       public bool IsValid {get; protected set;}
       public User LastChangedBy { get; protected set; }
       public DateTime LastChangedDate { get; protected set; }
    }

public SomeObject
{
   public Object Propery{get; set{IsValid = false; ...}}
   .....
   SomeBehaviorThatChangesObject(...)
   {
     //change the object
     IsValid = false;
   }
}

オプション3:私の目は最も実用的ですが、あまりDDDではありません

UnitOfWorkの永続化レイヤー、またはrepository.SaveChanges(UserchangedBy)などのリポジトリーで実行します。私、またはこの部分を実装する人は、それを忘れて、オブジェクトを無効な状態のままにする可能性があります...

public SomeRepository
    {
       public void Update (User changedBy)
       {...}
    }

誰が最後にエンティティを変更したかを確認できるのはごく普通のことですが、DDDを実装する良い例は見たことがありません。これはどうやるんですか?

更新 jgauffinsソリューションへの応答:ありがとう、Baseは実際にインターフェースを実装し、情報で過負荷にならないように非常に単純化しました。すべてのメソッドがすべての変更のパラメーターとしてユーザーオブジェクトを必要とするため、エレガントではないと感じています。突然、すべてのセッターを完全にプライベートにする必要があります...

それをリポジトリに入れることでうまくいくかもしれませんが、上記の議論は有効であり、私はそれについて考えていなかったので、まさにそれが私が尋ねた理由です。私は実際にいくつかのオブジェクトを変更する必要があるかもしれないサービスを計画しています。

それは大きな変化であり、何百ものメソッドとプロパティを変更しているので、私は自信を持っていたいです。また、この投稿で述べたように、1つのフィールドを設定するだけで、かなりの数のメソッドが作成されます。「set」は、ユーザーが変更の内容を十分に理解できるため、メソッドは必要ありません...私の直感しかし、あなたが正しいと言っているので、同じことをする別の方法があるかどうかを確認したかっただけです。

最終更新:

すべてのコメント、質問、および提案された解決策を読んで、LastChangedByフィールドとLastChangedDateフィールドの見方を再考する必要があることに気づきました。一部のオブジェクトでは、これは実際にはドメインに属していると感じるものに関連しています。他の多くの人にとって、それは私が探している本当に監査です。違いはわかりませんでした。

つまり、Documentオブジェクトは作成者以外の人が変更することができ、それは何らかの動作(作成者への通知など)によって支援されます。このような場合、私が興味を持っているドキュメントオブジェクトを最後に変更したのは実際には人ではありませんが、ドキュメントのコンテンツを編集したのは最後の人です。

これをLastChangedByと混合する代わりに、LastEditedByのフィールドを作成します。すべての変更を追跡する必要がある場合は、これが編集者のリストになることもあります。

もちろん、ほとんどの場合、これはLastChangedByと同じユーザーですが、作成者が入ってプロパティを変更し、さらに編集するためにドキュメントをロックするシナリオを見てください。次に、ドキュメントは実際には編集されていませんが、変更されており、監査の理由で追跡したいと思います。同じフィールドを使用して編集を追跡した場合、それは間違っています。私はそれを行うことができました。現在の要件は、作成者が最後にドキュメントを変更したのは誰かを確認できることですが、それは実際には正しい解決策ではありません。

監査を実装するために、私は次のことを行いました。

まず、基本クラスを実装する必要のあるインターフェイスに分割しました。lastChangedByおよびLastChangedDateは、監査が必要なオブジェクトが実装する必要があるIAuditableインターフェイスで定義されます。

次に、次のようにDbContext.SaveChanges()をオーバーライドします。

        public override int SaveChanges()
    {
        if(ChangeTracker.Entries<IAuditable>().Any())
            throw new InvalidOperationException
                (
                "Tried to save changes on an object that is needs to be Audited. Please provide the User that makes the changes!"
                );
        return base.SaveChanges();
    }

    public int SaveChanges(User changedBy)
    {
        var entries = ChangeTracker.Entries<IAuditable>().Where(entry=>entry.State == EntityState.Modified);
        foreach (var dbEntityEntry in entries)
        {
            dbEntityEntry.Entity.Audit(DateTime.Now, changedBy);
        }
        return base.SaveChanges();
    }

もちろん、これを行う方法は他にもありますが、少なくともこれは始まりのように思えました。

今、私は自分の質問がもっと良かったかもしれないことに気づきましたが、正直なところ、問題の一部がさまざまなオブジェクトのさまざまな理由で変更を追跡したいということであることに気づきませんでした。上記のドキュメントの例は、LastChangedByが実際にはドメインで設定または追跡する必要のあるものではない、もう1つのオブジェクトの1つにすぎませんが、上記のように、より特殊なものに書き直すことができます。

4

2 に答える 2

4

から始めましたThread.CurrentPrincipal。ただし、後で非同期処理に切り替える場合 (たとえば、ここで説明するようなコマンドを使用する場合) を使用することはできませんThread.CurrentPrincipal

しかし、最初に根本的な問題があります。

public abstract class Base
{
   public User LastChangedBy { get; protected set; }
   public DateTime LastChangedDate { get; protected set; }
}

それは基本クラスではありません。機能を追加するものではありません。代わりに、似たような名前のインターフェイスを変更する必要がありますITrackChanges

public SomeObject
{
   .....
   SomeBehaviorThatChangesObject(User changedBy, ...)
   AnotherBehaviorThatChangesObject(User changedBy, ...)
}

それが私が物事を行う方法です。なぜエレガントではないと思いますか?管理者またはバックグラウンド サービスは、そのアプローチでユーザーに代わって処理を実行できます。

アップデート

すべてのメソッドがすべての変更のパラメーターとしてユーザーオブジェクトを必要とするため、エレガントではないと感じています

良い。それはビジネス要件ですよね?プロパティを変更する必要がありLastChangedByます。したがって、それを提供する必要があります。

DDD ではパブリック セッターは禁止されていません。それらを使用できますが、この場合、変更ごとに と の両方を更新する必要がありますLastChangedByLastChangedDateそして、そのロジックは、呼び出し元のコードではなく、モデル自体によって適用される必要があります。したがって、パブリック セッターを使用することはできません。

于 2012-10-24T07:01:31.830 に答える
2

...または、ユーザーを設定してThread.CurrentPrincipal、インターフェイスの一部にしないこともできます。

これは、カスタム認証または AD 認証のいずれかを使用する Web サイトやデスクトップ アプリなど、あらゆる ID モデルでうまく機能します。

拡張するには、ユーザーが設定されていない場合にリポジトリなどをスローできます。また、ロールまたはクレームベースのアクセス許可を実装して、ユーザーの ID に基づいて特定の種類の更新を防止/許可することもできます。

于 2012-10-23T22:39:11.563 に答える