Подтвердить что ты не робот

Как избежать анемичных моделей домена или когда переместить методы из объектов в службы

У меня есть общий сценарий, что я ищу некоторые рекомендации от людей, более опытных с DDD и Domain Modeling в целом.

Скажем, я начинаю строить механизм блога, и первое требование состоит в том, что после публикации статьи пользователи могут начинать публиковать комментарии к нему. Это начинается нормально и приводит к следующему дизайну:

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

Поэтому я в основном ищу совет от людей, более опытных в области моделирования доменов. Если у меня отсутствует более очевидное решение, сообщите мне:)

Мне вообще не нравятся оба решения, если честно, потому что опция Service менее понятна. Я больше не могу добавлять комментарий к экземпляру статьи без наличия доступной статьи. Это также кажется менее естественным, поскольку AddComment кажется таким очевидным методом для типа статьи.

В любом случае я с нетерпением жду чтения ввода. Спасибо заранее.

4b9b3361

Ответ 1

Я думаю, что эта конкретная проблема может быть решена элегантно с Событие домена.

Ответ 2

Считаете ли вы, что контроллер статьи по существу передает сообщение вверх/вывешивает событие? Тогда любые "прослушиватели, опубликованные в журнале", будут потреблять это сообщение и соответственно реагировать; в вашем конкретном случае оповещение по электронной почте будет прослушивать эти события и настраиваться для этого. Таким образом, биты публикации статьи не должны знать ничего о битах уведомления по электронной почте.

Ответ 3

Просматривая этот прекрасный вопрос, я заставил читать использование шаблона модели домена из Udi в MSDN.

HTH помогает другим пользователям.

Я пытался разобраться, как задавать этот вопрос, но несколько раз путался. Ваш вопрос, конечно же, нет! Благодаря

Ответ 4

Без использования событий домена вы можете использовать шаблон 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() и добавить AddComment для статьи и комментарий ICommentAddedProcessor.

Ответ 5

Я думаю, всякий раз, когда эксперт домена использует слово "когда", нужно учитывать события домена или шину событий, это был классический пример в этом смысле.

Я написал подробный ответ , в котором описывается, когда использовать шины событий, возможно, это хорошо читается в этом разделе