views:

442

answers:

3

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.

+1  A: 

Have you considered having the article controller essentially pass a message up / post an event? Then any "article-posted-event-listeners" would consume that message and respond accordingly; in your specific case, an email notifier would be listening for those events and be configured to do so. This way the article posting bits don't need to know anything about the email notifying bits.

Mikeb
+10  A: 

I think this particular issue can be solved elegantly with a Domain Event.

Mark Seemann
After read Udi's three articles I think this will solve my problem nicely. However, I am still a little confused as to where the domain events should be registered. Should my controller register them in the ctor? Should the domain events be registered at Application_Start?
Matt Hidinger
Well, if you dig through all the comments to that post, you will find a mini-discussion between me and Udi about whether the event service should be static or not. Personally, I would make it an instance service and inject it into the Controller as a dependency. I guess Udi would register it in Global.asax...
Mark Seemann
This is such a basic scenario, and Domain Event is something that wasn't even present in Evans book. So how did people cope with it?
deadbeef
A: 

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

Rippo