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.
-
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(); }
-
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:
- 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? -
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(""); } }
-
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.
Aucun commentaire:
Enregistrer un commentaire