4

Imagine you build a social network. There are users who can add other users as friends. How do you model this in DDD? Naturally you cannot simply have a list of friends in the User class, because any friend loop would cause infinite recursion on getting such a user from the repository.

How would you change your model if you needed to track friend requests which can be pending, canceled, accepted or declined?

SnakE
  • 2,355
  • 23
  • 31

3 Answers3

6

hmm... Actually it's quite easy to do what you've asked, and your situation is a standard one. You don't have to store actual User object in Friendslist of your User aggregate, just put there IDs of users who are friends for the User.

This is one of the rules of aggregate implementation proposed by Vaugh Vernon: link to other aggregate and entities by their ID. So no loops, just list of IDs.

In that situation then somebody become a friend to somebody you have to change two aggregates at one time. It can be undesirable behavior because change can't occur instantly in one transaction. But for this case you have Domain Events and friend requests can be easily modeled: your aggregates can communicate with each other with FriendshipRequested, FriendshipAccepted, FriendshipCancelled or FriendshipDeclined events and change their state correspondingly.

In this case you also receive logs and notifications for free.

Boris Tsema
  • 571
  • 3
  • 7
  • 2
    Thank you. I think your answer summarizes it best. My confusion stemmed from the fact that I thought user IDs were persistence details outside the domain. While in fact identities were integral parts of the domain. – SnakE Feb 08 '13 at 15:02
1

A User could have a list of Friends. A Friend could consist of a UserId, FriendType, GroupId, etc.

A User could also have a list of FriendRequests. A FriendRequest could have a UserId, RequestMessage, RequestStatus etc.

User, Friend and FriendRequest could all be part of the same Aggregate. But there could be some duplication by doing so. Another option would be to have one FriendRequest for both users involved, and each user would have a list of received and sent FriendRequest IDs.

This is just to give you some ideas because in DDD, your design will depend highly about how your app will work, and which features are needed. ;-)

Meta-Knight
  • 17,626
  • 1
  • 48
  • 58
  • Your `Friend` is obviously a domain object. I thought domain objects couldn't have any IDs in them, not from the domain perspective anyway. If your `Friend` implies a reference to a `User` there should be that reference, and we end up with those loops again, just with `Friend`s in the middle. – SnakE Feb 08 '13 at 02:48
  • 1
    I would suggest that you try to limit actual instance references in an aggregate to those entities that constitute the aggregate. So if you were to delete the aggregate *all* instances should be deleted. There is absolutely nothing wrong with storing the reference of another aggregate as an ID. Of course, transient instance references would be fine. – Eben Roux Feb 08 '13 at 07:23
  • @SnakE: I recommend reading "Effective Aggregate Design" by Vaughn Vernon: http://dddcommunity.org/library/vernon_2011 . In part II, he states: "Prefer references to external aggregates only by their globally unique identity, not by holding a direct object reference." – Meta-Knight Feb 08 '13 at 14:03
1

That would very much depend on where you consistency boundaries need to be. Which would also therefore depend on what business rules you have.

While Meta-Knight has FriendRequest in the same Aggregate I would have it as its own and use events to communicate between the Aggregates therefore making a Person and there FriendRequests eventually consistent. This would allow you to do something like.

public class DomainRouter {
    public void When(FriendRequestCreated event)
    {
            //Send command to each Person to record request
    }

    public void When(FriendRequestAccepted event)
    {
            //Send command to Person to record request accepted and add friend.
            //Send comamnd to Person who accepted to add Friend
    }

    public void When(FriendRequestDeclined event)
    {
            //Send command to update Friend request on person.
            //Send command to Person who declined to record they have declined?
    }
}

The information on Person would therefore just be a record of state. The FriendRequest Aggregate would be where all the process actually happens.

What is important in DDD is to think about behavour. A FriendRequest can be Requested, Withdrawn, Accepted and Declined. What can a Person do? Does a Person need to be DDDd or could you make it nice and simply CRUD + store info into graph database etc.

Maybe you want to model it that in a way where you can go person.requestFriendAcceptance(Person id) in which case the router would probably just handle the FriendRequestCreated event by notifying the friend of a FriendRequest.

Alistair
  • 1,064
  • 8
  • 17
  • Thank you for the answer. However you do not address my main question: how to record a link between two `Person`s? Assuming that a `Person` can do tons of other stuff in a social network. – SnakE Feb 08 '13 at 02:56
  • Makes sense in terms of consistency boundaries. But what if you wanted business logic that disallowed user B from creating a friendrequest to userA if userA has already submitted a request? – Johnny Z Jan 07 '17 at 02:26
  • @JohnnyZ There is nothing which says a FriendRequest is a directional relationship. You could essentially have it represent the request going in both directions and order the person id's consistently so you always get the same id irrespective of which party creates the request. – Alistair May 02 '18 at 20:24