49

我有一个常见的场景,我正在寻找对 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) 修改 Article 以要求 IEmailService(在 ctor 中?)或从对我的 DI 容器的静态引用中获取 EmailService

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,我无法再向 Article 实例添加评论。它也感觉不太自然,因为 AddComment 似乎是 Article 类型的一个明显的方法。

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

4

5 回答 5

24

我认为这个特殊问题可以通过Domain Event优雅地解决。

于 2009-09-28T19:03:37.267 回答
2

您是否考虑过让文章控制器本质上传递消息/发布事件?然后任何“article-posted-event-listeners”都会使用该消息并做出相应的响应;在您的特定情况下,电子邮件通知程序将监听这些事件并被配置为这样做。这样,发布位的文章就不需要知道有关电子邮件通知位的任何信息。

于 2009-09-28T19:02:54.653 回答
1

通过这个优秀的问题,我在 MSDN 上阅读了来自 Udi 的 Employing the Domain Model Pattern 。

HTH 帮助其他用户。

我一直在试图弄清楚如何问同样的问题,但几次都把自己弄糊涂了。你的问题肯定不是!谢谢

于 2010-02-01T07:59:57.017 回答
0

在不使用域事件的情况下,您可以使用双重调度模式并将 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 上,而是使用 CommentAdded() 方法将域服务变成类似 ICommentAddedProcessor 的东西,并让文章上的 AddComment 接受评论和 ICommentAddedProcessor。

于 2014-03-31T19:27:48.510 回答
0

我认为每当领域专家使用“何时”这个词时,必须考虑领域事件或事件总线,从这个意义上说,这是一个经典的例子。

我已经写了一个详细的答案,描述了何时使用事件总线,围绕这个主题可能是一个很好的阅读

于 2014-10-28T05:38:48.717 回答