Let's say my domain has a
User Entity that has a many to many relationship with itself representing "Friends"
public class User
public int Id;
public IEnumerable<User>? Friends;
In my application layer, I have a service that makes 2 User friend together and the only requirement is that they are not already friends:
public class UserServies
public void AddFriend(int user1Id, int user2Id)
var user1 = _repository.GetUserById(user1Id);
var user2 = _repository.GetUserById(user2Id);
// use user1 and user2 in my domain layer to enforce business rules...
GetUserById would execute a SQL query like this:
SELECT u.* FROM Users u
LEFT JOIN Users_Friends uf ON u.Id = uf.UserId
LEFT JOIN Users f ON f.Id = uf.FriendId
WHERE u.Id = @Id
So far so good! But let's say the requirements changed and I would now need to access
user1.Friends[i].Friends, this would result in a runtime error since we didn't load that data. I'm having trouble designing the domain layer in a way to avoid these kind of problems. I've thought of a few solutions, but none that makes me happy:
Eagerly load all relationships all the time
Bad: Performance wise it's terrible as it would require me to load all users all the time.
Only load the necessary data required by my domain business rules
Bad: This require way too much coupling between the data loader and the domain rules using said data. It would also be incredibly easy for a business rules change to result in a runtime error because someone didn't realize that the data loaded was no longer adequate (such as in my example above).
Use lazy loading in domain layer
Bad: Now the domain layer is no longer pure and is coupled to whatever persistence mechanism the infrastructure is using.
Use specific DAO object for each business operations
In this solution instead of dealing with a
AddFriend would be dealing with a
AddFriendUserDAO would also need to have all its relationships be specific
AddFriend DAO for it to works. Needless to say, it would increase the amount of code I need to write exponentially.
Do you have any recommended design pattern to deal with this scenario? I feel like it should be a pretty common scenario but I just don't know the right words to search for the solution.