How to avoid anemic domain models, or when to move methods from the entities into services

Viewed 2859

I have a common scenario that I am looking for some guidance from people more experienced with DDD and Domain Modeling in general.

Say I start out building a blog engine, and the first requirement is that after an Article is posted, users can start posting Comments on it. This starts fine, and leads to the following design:

public class Article
{
    public int Id { get; set; }

    public void AddComment(Comment comment)
    {
        // Add Comment
    }
}

My MVC Controller is designed like this:

public class ArticleController
{
    private readonly IRepository _repository;

    public ArticleController(IRepository repository)
    {
        _repository = repository;
    }

    public void AddComment(int articleId, Comment comment)
    {
        var article = _repository.Get<Article>(articleId);
        article.AddComment(comment);
        _repository.Save(article);
        return RedirectToAction("Index");
    }
}

Now everything works fine, and it meets the requirement. Next iteration we get a requirement that every time a Comment is posted, the blog author should get an email notifying him.

At this point, I have 2 choices that I can think of. 1) Modify Article to require an IEmailService (in the ctor?) or obtain an EmailService from a static reference to my DI container

1a) Seems pretty ugly. I believe it breaks some Domain model rules that my entities are aware of services?

public class Article
{
    private readonly IEmailService _emailService;

    public Article(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void AddComment(Comment comment)
    {
        // Add Comment

        // Email admin
        _emailService.SendEmail(App.Config.AdminEmail, "New comment posted!");
    }
}

1b) Also seems ugly, I now require a configured DI container which is accessed statically.

public class Article
{
    public void AddComment(Comment comment)
    {
        // Add Comment

        // Email admin
        var emailService = App.DIContainer.Resolve<IEmailService>();
        emailService.SendEmail(App.Config.AdminEmail, "New comment posted!");
    }
}

2) Create an IArticleService and move the AddComment() method to this service instead of on the Article Entity itself.

This solution is cleaner I believe, but Adding a comment is now less discoverable and requires an ArticleService to perform the work. It seems like AddComment should belong on the Article class itself.

public class ArticleService
{
    private readonly IEmailService _emailService;

    public ArticleService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void AddComment(Article article, Comment comment)
    {
        // Add comment

        // Email admin
        _emailService.SendEmail(App.Config.AdminEmail, "New comment posted!");
    }

}


public class ArticleController
{
    private readonly IRepository _repository;
    private readonly IArticleService _articleService;

    public ArticleController(IRepository repository, IArticleService articleService)
    {
        _repository = repository;
        _articleService = articleService;
    }

    public void AddComment(int articleId, Comment comment)
    {
        var article = _repository.Get<Article>(articleId);
        _articleService.AddComment(article, comment);
        _repository.Save(article);
        return RedirectToAction("Index");
    }
}

So I am basically look for advice from people more experienced in domain modeling. If I am missing a more obvious solution please let me know :)

I generally dislike both solutions to be honest, because the Service option is less discoverable. I can no longer add a Comment to an instance of an Article without having an ArticleService available. It also feels less natural, since AddComment seems like such an obvious method on the Article type.

Anyway I look forward to reading the input. Thanks in advance.

5 Answers

Looking through this excellent question, has lead me to read Employing the Domain Model Pattern from Udi on MSDN.

HTH helps other users.

I been trying to work out how to ask this same question but managed to confused myself several times. Your question certainly is not! Thanks

Related