3
votes

I'm working with two different aggregate roots: Post and Question. Both of them have a Category.

So far I have implemented it as a shared entity (which I'm not sure if is a correct design in DDD).

public class Post
{
    public Guid Id { get; private set; }

    public Category Category { get; private set; }

    public string Title { get; private set; }

    public string Body { get; private set; }
}

public class Question
{
    public Guid Id { get; private set; } 

    public Category Category { get; private set; }

    public string Title { get; private set; }

    public string Body { get; private set; }
}

public class Category
{
    public int Id { get; private set; }

    public string Name { get; private set; }

    public string Key { get; private set; }
}

Note: I'm aware I'm falling into primitive obsession anti-pattern, and I have plans on refactor the primitives into ValueObjects.

After read this post DDD: Share entity with multiple aggregate roots I'm thinking that maybe I should convert the Category in a ValueObject (with multiple fields).

In theory Category could be an Entity with its own lifecycle, but reality is that I don't really add/remove/update categories.

Is it possible to use a shared Entity on DDD? Or I better rather use a ValueObject?

2

2 Answers

2
votes

Lets deal with one aggregate first: Post

Now to answer your question:

Is it possible to use a shared Entity on DDD? Or I better rather use a ValueObject?

It depends on what you will do with Category.

Scenario 1:

You have a feature(or page) in your application to show all posts of a category. I would go with the following design:

public class Category
{
    public int Id { get; set; }
    //this is my in-memory database. Use repository and service to adjust yours
    public static List<Post> Posts;

    public Category()
    {
        Posts = new List<Post>();
    }

    public void AddPost(Guid id, string title, string body)
    {
        var post = new Post(id, title, body, this.Id);
        //saving the post into in-memory. Perhaps you can check some business logic inside Post entity
        Posts.Add(post);
    }

    // You can retrieve all posts of a single category
    public IEnumerable<Post> GetAllPosts()
    {
        return Posts.Where(x => x.CategoryId == this.Id);
    }
}

public class Post
{
    public Guid Id { get; private set; }
    public string Title { get; private set; }
    public string Body { get; private set; }
    public int CategoryId { get; private set; }

    public Post(Guid id)
    {
        Id = id;
    }

    public Post(Guid id, string title, string body, int categoryId)
    {
        //I prefer to pass guid into domain from external services.
        //Using this way, your service will have the id to return to upper layers.
        //Alternatively you can create new guid here on your own
        Id = id;
        Title = title;
        Body = body;
        CategoryId = categoryId;
    }

    // you can retrieve a post detail
    public Post GetPost()
    {
        return Category.Posts.FirstOrDefault(x => x.Id == this.Id);
    }
}

I can see only one aggregate root in this scenario: Category.

Scenario 2:

You have posts page, from there users can view detail post. Additionally, every post has a category which will be shown somewhere on that detailed page. You can have following simple design:

public class Post
{
    public Guid Id { get; private set; }
    public string Title { get; private set; }
    public string Body { get; private set; }
    public string CatKey { get; private set; }

    public Post(Guid id)
    {
        Id = id;
    }

    public Post(Guid id, string title, string body, string catKey)
    {
        //I prefer to pass guid into domain from external services.
        //Using this way, your service will have the id to return to upper layers.
        //Alternatively you can create new guid here on your own
        Id = id;
        Title = title;
        Body = body;
        //I don't even bother with category id. This is a simple value object, you can store all of your categories
        //into a hashtable of key-value
        CatKey = catKey;
    }

    // you can retrieve a post detail
    public Post GetPost()
    {
        //get your post detail from repo
    }
}

Hope you can make your decision now.

1
votes

The main question of Entity vs ValueObject is would two instances of the Category with the same values need to be tracked differently? The classic example is a dollar bill - in most instances, the serial number (ID) doesn't matter, and one dollar is the same as another (ValueObject). If your domain is collecting rare bills, though, that would change.

I'd suspect not in your case, since it appears Category is really just comprised of the name and key. If the Category of a Post changes, do you need to track what the Category previous was?