简体   繁体   中英

DDD - Enforce invariants for associations inside the aggregate

I'm trying to understand the DDD concepts and stumbled when I wanted to update an entity which is part of the collection associated to root entity. I'm trying to enforce invariant from root entity but I found there is a way to bypass the validation before saving it to database.

Implemented the Domain model based on the one of the DDD rules:

The root Entity has global identity and is ultimately responsible for checking invariants .

I have a Me class root entity and it has a collection of entities named Friends. There are friend types like Best Friend, Just Friend, Work Friend

Invariant: The friends collection can contain any number of Just Friend & Work Friend but shall contain only one Best friend .

public class Me : Entity, IAggregateRoot
{
    public string Name {get; }

    private List<Friend> friends;
    public IReadonlyCollection<Friend> Friends => friends;

    public Me(string name)
    {
      Name = name;
      friends = new List<Friend>();
    }

    public void AddFriend(string name, string type)
    {
      //Enforcing invariant
      if(CheckIfBestFriendRuleIsSatisfied(type))
      {
         Friend friend = new Friend(name, type);
         friends.Add(friend);
      }
      else
         throw new Exception("There is already a friend of type best friend.");
    }

    public void UpdateFriend(int id, string name, string type)
    {
      //Enforcing invariant
      if(CheckIfBestFriendRuleIsSatisfied(id, type))
      {
         Friend friend = firends.First(x => x.Id == id);
         friend.SetType(type);
         friend.SetName(name);
      }
      else
         throw new Exception("Cannot update friend.");
    }
}

public class Friend : Entity
{
   public string Name {get; }
   public string Type {get; }

   public Friend(string name, string type)
   {
     Name = name;
     Type = type;
   }

   public void SetType(string type)
   {
       Type = type;
   }

   public void SetName(string name)
   {
       Name = name;
   }
}

Scenario: Me root entity has two friends in collection, one of type best friend and other of type just friend. Now if you try to change type on "just friend" entity from "Just Friend" to " Best Friend " code should not allow and throw exception, since this is a business rule violation.

  1. Normal implementation to enforce invariant while updating friend.

     public void DomainService_UpdateFriend() { var me = repo.GetMe("1"); me.UpdateFriend(2,"john doe","Best Friend"); //Throws Exception repo.SaveChanges(); }
  2. Business rule bypass implementation

     public void DomainService_UpdateFriend() { var me = repo.GetMe("1"); var friend = me.Friends.First(x => x.Id == 2); friend.SetType("Best Friend"); // Business rule bypassed friend.SetName("John Doe"); repo.SaveChanges(); }

This brings me to Questions:

  1. Is the design on the models is wrong?
    If yes, how is it is wrong and what should be the correct implementation?
    If no, then is this not a violation of afore mentioned DDD rule?
  2. Will the following be the proper implementation

     public class Me : Entity, IAggregateRoot { ...Properties ...ctor ...AddFriend public void UpdateFriend(int id, string name, string type) { Friend friend = firends.First(x => x.Id == id); if(friend != null) { friend.SetNameAndType(name,type, this);//Passing Me root entity } else throw new Exception("Could not find friend"); } } public class Friend : Entity { ...Properties ...ctor //passing root entity as parameter or set it thru ctor public void SetupNameAndType(string name, string type, Me me) { if(me.CheckIfBestFriendRuleIsSatisfied(id, type)) { Name = name; Type = type; } else throw new Exception(""); } }
  3. The place of enforcing invariant/validation varies from adding an entity to a collection vs updating an entity with in the collection? That is validation while Addfriend() is correct but not while UpdateFriend().

Does this remind us another rule in DDD which states When a change to any object within the Aggregate boundary is committed, all invariants of the whole Aggregate must be satisfied.

  • How does this Implementation look like?

  • Does using specification/notification pattern and validating the domain model on domain service addresses the issue?

  • Using specification pattern would be more apt when there are multiple contexts or multiple states of model. But what if there are no multiple context and states?

    While I appreciate answers of all kind, I'm looking for code implementation to get it right. I have seen number of questions on SO but none of them show code implementation.

The friends collection can contain any number of Just Friend & Work Friend but shall contain only one Best friend.

That suggests that you might want a data structure like:

class Me {
    List<Friend> friends;
    Friend bestFriend;
    // ...
}

That is, if you design your model such that invalid states are not representable , then you get some assistance in catching errors in your domain logic.

At a fundamental level, we're dealing with a state machine. The aggregate begins in some initial state A. We invoke some method on the root, and either we remain in the same state, or we transition to some new state B that also satisfies the entire invariant.

It shouldn't feel like we are doing anything new here -- this is just good "object oriented programming". Any method we invoke on an object should satisfy its post conditions. The fact that we've chosen to carve up "the aggregate" into a composition of multiple objects really doesn't change that.

Does using specification/notification pattern and validating the domain model on domain service addresses the issue?

That sounds to me like a bunch of unnecessary complication.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM