2009-09-28 62 views
45

我有一个常见的场景,我正在寻找一些有关DDD和领域建模更有经验的人的一些指导。如何避免贫血域模型或何时将方法从实体移动到服务中

假设我开始构建博客引擎,第一个要求是在发布文章后,用户可以开始发布评论。这将启动罚款,并导致了以下设计:

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

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

我的MVC控制器的设计是这样的:

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"); 
    } 
} 

现在一切正常,并符合要求。接下来的迭代中,我们得到一个要求,每次发布评论时,博客作者都应该收到一封电子邮件通知他。

在这一点上,我有两个选择,我可以想到。 1)修改文章要求IEmailService(在ctor?中)或从静态引用获取EmailService到我的DI容器

1a)看起来很丑陋。我相信它打破了我的实体知道服务的一些域模型规则?

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)也似乎丑陋,我现在需要一个静态访问配置DI容器。

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)创建一个IArticleService并将AddComment()方法移动到此服务而不是文章实体本身。

我相信这个解决方案更干净,但添加评论现在不易发现,需要ArticleService来完成这项工作。似乎AddComment应该属于Article类本身。

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"); 
    } 
} 

所以,我基本上寻找更多领域建模经验的人的意见。如果我错过了一个更明显的解决方案,请让我知道:)

我通常不喜欢这两种解决方案是诚实的,因为服务选项不易发现。如果没有可用的ArticleService,我不能再向文章的实例添加评论。它也感觉不那么自然,因为AddComment看起来就像是一种明显的Article类型的方法。

无论如何,我期待阅读输入。提前致谢。

+1

+1的解决方案#2 – JuanZe 2009-09-28 19:02:57

+1

+1很好看的一个清晰的问题 – Rippo 2010-02-01 08:01:23

回答

24

我认为这个问题可以用Domain Event来优雅地解决。

+0

读乌迪的三篇文章后,我认为这将很好地解决我的问题。不过,对于域名事件应该注册在哪里,我仍然有点困惑。我的控制器是否应该将它们注册到ctor中?应该在Application_Start注册域事件吗? – 2009-09-28 19:57:21

+1

好吧,如果你仔细查看那篇文章的所有评论,你会发现我和Udi之间关于事件服务是否应该是静态的迷你讨论。就我个人而言,我将它作为一个实例服务,并将其作为依赖注入到Controller中。我想Udi会在Global.asax中注册它... – 2009-09-28 20:47:54

+3

这是一个这样的基本场景,而域名事件甚至还没有出现在埃文斯的书中。那么人们如何应对呢? – aaimnr 2009-12-05 13:50:50

2

您是否认为文章控制器本质上是通过邮件发送/发布事件?然后,任何“文章发布事件监听器”都会使用该消息并作出相应的响应;在您的具体情况下,电子邮件通知程序会监听这些事件并进行配置。通过这种方式,文章发布位不需要知道关于电子邮件通知位的任何信息。

1

回顾这个优秀的问题,让我在MSDN上从Udi读取Employing the Domain Model Pattern

HTH帮助其他用户。

我一直在努力解决如何问这个同样的问题,但设法多次困惑自己。你的问题当然不是!谢谢

0

如果不使用域事件,可以使用Double Dispatch模式并将AddComment逻辑放入域服务中。

这是它会是什么样子:

public class Article 
{ 
    public void AddComment(Comment comment, IAddCommentProcessor commentProcessor) 
    { 
     commentProcessor.AddComment(this, comment); 
    } 
} 

public interface IAddCommentProcessor 
{ 
    void AddComment(Article article, Comment comment); 
} 

public class AddCommentAndEmailProcessor : IAddCommentProcessor 
{ 
    private readonly _emailService; 
    public AddCommentAndEmailProcessor(EmailService emailService) 
    { 
     _emailService = emailService; 
    } 

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

     // Email 
     _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); 
     article.AddComment(comment, new AddCommentAndEmailProcessor(ServiceLocator.GetEmailService())); // Or you can use DI to get the Email Service, or any other means you'd prefer 
     _repository.Save(article); 
     return RedirectToAction("Index"); 
    } 
} 

如果你愿意,你可以保持在文章的AddComment的添加评论的逻辑,而是使域名服务变得像ICommentAddedProcessor用CommentAdded( )方法,并对Article进行AddComment评论和ICommentAddedProcessor。

0

我认为,只要领域专家使用这个词,当“”必须考虑域事件或事件总线时,这是一个典型的例子。

我已经写描述时使用事件总线的详细answer,这可能是围绕这个话题