8

I'm new to Domain Driven Design and I have some doubt about some concepts (hoping that this is the right place to ask this).
I know that in DDD I should avoid an anemic model, so thinking about a social network model who should make (save) the friendship between two friends?
I imagine the situation like having a class representing the Users (using a Java-like syntax):

class User{
    String username
    List<User> friends
}

So, should it have a method to add a friend?

class User{
    void friendship(User friend)
}

or should I have to use a service to do it?

class UserService{
     void friendship(User user, User user2)
}
Matster2
  • 137
  • 1
  • 14
rascio
  • 8,968
  • 19
  • 68
  • 108
  • Your class is similar with Node class from graph theory. See http://stackoverflow.com/questions/14764196/ddd-and-homogeneous-many-to-many-relationship – Mihai8 Apr 02 '13 at 23:47
  • at the matter of fact it is just a node :) and because of this I'm using a graph database, to link each entity...the response to the question you give me is to store the IDs, but in this way don't I lose benefits of using a graph db?? – rascio Apr 02 '13 at 23:57

7 Answers7

6

My thought is that this is an example of what in relational database theory is termed a "weak entity". A Friendship could be identified solely by the identifiers of the two Users involved in the friendship, but could have its own properties such as when it was created and what type of relationship it is.

I would make this its own entity, and probably hide it behind a facade exposed by the User object:

class User {
    protected List<Friendship> _friendships { get; private set; }

    public IEnumerable<User> Friends {
        get { return _friendships.Select( /* get other user */ ); }
    }

    public void AddFriend(User otherUser) {
        // check to see if friendship exists
        // if not, create friendship
        // do other friendshippy things

        // make sure the other user knows about our friendship 
        // and gets to do its friendshippy things
        otherUser.AddFriend(this);
    }
}
Matt Mills
  • 8,692
  • 6
  • 40
  • 64
  • I'm also thought to insert a Friendship class to store informations (like when the friendship was created, etc...) but in the way you wrote I should have a Controller that add the friend using the User class and then persist this change using a UserRepository, so why isn't better to use a Service to add the friend and persist the change? – rascio Apr 03 '13 at 00:00
  • Primarily because creating new entities is fundamentally "domain logic". Here, the `User` on which `AddFriend` is called *must* know about the other `User`, and by definition has all of the information required to create a new `Friendship`. Imagine that `User`'s primary ID were `protected`, for instance (something I like to do whenever possible in DDD for exactly these reasons) - how would the service create the `Friendship`? – Matt Mills Apr 03 '13 at 00:16
  • mmmm yes it make sense...but if I set the User primary ID as protected how can I retrieve it? If I would create a link in a web page to the detail of a User, and if the primary ID isn't public how can I do it? Using the username as Id of the class in the application and just the primary id (from a sequence for example) to do database related operations? In this case the Friendship is an Aggregate, right? – rascio Apr 03 '13 at 00:28
  • **That** is where the service comes in - you can get a *view* of the `User`s - maybe their ID and User Name, and then when you need to do something with a user you can look it up by ID and do whatever you need to with it **as a** `User`. That's how you stay away from making the domain anemic - if your service doesn't know anything about the internals of a `User`, there's no way it can do what a `User` is supposed to do. As an example - if you pass me a `User` and I can't look at its ID, I can't go around its interface to muck with its data in the database. – Matt Mills Apr 03 '13 at 00:34
  • I think a service may make sense here, but it depends if you treat `Friendship` as an entity within a `User` aggregate or as a separate aggregate. If it's a separate aggregate (perhaps with two `User` entities that could also be aggregates), then a service makes sense to orchestrate the FriendshipFactory and insertion into FriendshipRepository. Graphs are tricky in DDD when Nodes and Edges both have clear identity and useful properties. – Steven Hood Apr 03 '13 at 05:43
  • 1
    To answer the question about whether a `Friendship` is an aggregate root - I don't see it that way. A `Friendship`, in my opinion, is explicitly defined as a relationship between two `User`s - it does not have an identity of its own outside of that context. – Matt Mills Apr 03 '13 at 14:30
  • Isn't the friendship a value object? – Aheho Jan 01 '16 at 01:16
5

I would use something like

public sealed class Username : IEquatable<Username> { /* string wrap here */ }
public class User
{
    private readonly Username _username;
    private readonly HashSet<Username> _friends;
    public User(Username username)
    {
        if (null == username) throw new ArgumentNullException("username");
        _username = username;
        _friends = new HashSet<Username>();
    }

    public Username Name { get {return _username; } }
    public void Befriend(User user)
    {
        if (null == user) throw new ArgumentNullException("user");
        _friends.Add(user.Name);
    }

    public bool IsFriendsOf(User user)
    {
        if (null == user) throw new ArgumentNullException("user");
        return _friends.Contains(user.Name);
    }
}

Note that no collection is exposed by User, according to the Law Of Demeter. In case you'd really need them I would expose an IEnumerable<Username> for friends.

Moreover, in DDD, all query and commands should be part of the ubiquituous language (this is why I used Befriend instead of AddFriend).

However, let me say that this look a bit too CRUD to require a DDD. If you don't need (at least) a domain expert to understand the domain, you don't need DDD at all. If you don't need DDD, it becomes the most expensive error in your project.

edit
Let's suppose that the domain expert states that "friendship is always reciprocal" (as per guillaume31's suggestion): by modeling idempotent commands, you can ensure such a business rule very easily. The Befriend command becomes:

public void Befriend(User user)
{
    if (null == user) throw new ArgumentNullException("user");
    if(_friends.Add(user.Name))
    {
        user.Befriend(this);
    }
}

You can always model idempotent commands with such properties, but sometimes it requires a bit more of analysis to ensure that their arguments and their internal state provide everything they need.

Giacomo Tesio
  • 7,144
  • 3
  • 31
  • 48
4

I think; a friendship is an aggregate root itself. It can be directly created within an application service or creation can be delegated to a domain service.

A domain service can ask user aggregates for validation, if a user specific validation requires. Or dispatch both user aggregates to friendship aggregate creator/constructor.

Then a friendship repositoy can easily return a list of friends for a given user.

Even a friendship aggregate does not have rich model or behaviour; it has a seperate consistency boundary.

Moreover, If we use eventsourcing; one can listen friendshipCreated events; and notify both users about situation.

Safak Ulusoy
  • 319
  • 3
  • 7
2

This is similar to the transferFunds() problem in the banking domain - should you call that method on the source account or the destination account ? And should an Account be able to manipulate another Account's Balance in the first place ?

DDD Domain Services come in handy when some behavior just doesn't seem to fit in an existing entity or manipulates several entities. This doesn't make your domain anemic since the service is part of the Domain, and having a limited number of entities with no behavior doesn't mean your entire model is anemic anyway.

If the Friendship relationship is reciprocal from the start, it may be more logical to delegate friendship creation to a Domain Service rather than having one User modify another User's list of friends.

Otherwise, an addFriend() method will work fine.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
2

A weak or anaemic domain model just means your "domain objects" are DTOs, with no behaviour. What you've basically got is the Transaction Script pattern, where DTOs are loaded, modified and saved again. CRUD, to put it another way. This is fine for a lot of apps, which don't have complex enough rules to benefit from a DDD-approach.

Domain objects should encapsulate behaviour. That is their essence. Any public state (it is possible to have no public getters or setters) should be readonly, and if you want to mutate state, you call a method that relates to a use-case/business requirement.

All your "domain logic" should be in these classes. Domain logic is just a fancy name to describe the rules and operating parameters of your chosen domain. Be it banking, retail, HR etc. When your domain experts explain the "Pay By Card" user-case and tell you "We can't open the till until the PDC machine has contacted the bank.", that's a business rule/invariant/piece of domain logic.

You would normally assemble you domain objects (made up of Entities and Value Objects) into Aggregates, which define a boundary within which a given set of rules must be satisfied. The Entity that is the root of this domain object graph is known as the Aggregate Root, and it is only to Aggregate Roots that other objects may hold references. In your case, User is an Entity, and as it's the only object in the Aggregate it is also the Aggregate Root. So for example:

public class User // Entity and also the Aggregate Root 
{
    private readonly IList<Friendship> _friends = new List<Friendship>();

    public void Befriend(User user)
    {
        _friends.Add(new Friendship(/* Date? */, user));
    }

    public class Friendship // Entity
    {
        // ... Date?
        private User _friend { get; private set; }

        public Friendship(/* Date? */, User friend)
        {
            _friend = friend;
        }
    }
}

This isn't a great example really because in theory you'd need to call this for each of two friends in a pair, but any transaction should only carry out one operation, not two. In this case, you introduce the concept of Process Managers. These are yet more objects that deal with the coordination of what is essentially a long-running transaction (where two friends become friends with each other). You'd probably create a Friendship (as an Aggregate Root) and it's creation would spawn some sort of event-driven process where the friends involved are loaded, befriended, and saved.

Neil Barnwell
  • 41,080
  • 29
  • 148
  • 220
  • 2
    mmmm I don't think the friendship is an aggregate root...it hasn't a behavior, just links to other users that are aggregates, so it should have only soft links to the users. With the concept of event sourcing + cqrs this is an easy problem to solve, the `User` have a `becomeFriend(User)` method that doesn't modify its state, but fire a FriendshipEvent that have the _identity_ of both users, then these data can be easily stored on a graph db (for the nature of information, otherwise can be a sql, or another kind of db). – rascio Mar 24 '14 at 23:57
1

The friendship between two users should be a separate aggregate (AR). As an ID of that AR I recommend to use users' IDs. This way you have small aggregate with low pressure which you could scale/partition. Same thing for messages sent between users. Every message is an aggregate.

This is how we designed it and it was successful.

Good luck

mynkow
  • 4,408
  • 4
  • 38
  • 65
1

Considering the unlimited amount of friendships one user can have, I don't think it should be modeled as a value object within the user AR. ARs should be as small as possible, and loading 10k of friends each time you want to alter the state isn't something ideal.

I'm with the option to have Friendship and FriendRequest as separate roots. A FriendRequest is something that lives in a single direction containing a from_user_id and a to_user_id. It can be ignored or rejected, or accepted. On acceptance, a 2-way friendship is established. Friendships are searchable by a materialised view (in for example elasticsearch), enhanced with the user name eventually updated by the User profile update events. For the user UI in case a conditional befriend-button or friendship-requested indicator is required, one can maintain a link table/search view with the Ids of the Friendship and/or the FriendRequest.

Pepster
  • 1,996
  • 1
  • 24
  • 41