45 votes

Comment éviter les modèles de domaine anémiques, ou quand déplacer les méthodes des entités vers les services.

J'ai un scénario commun pour lequel je cherche des conseils auprès de personnes plus expérimentées en matière de DDD et de modélisation de domaine en général.

Imaginons que je commence à construire un moteur de blog, et que la première exigence soit qu'après la publication d'un article, les utilisateurs puissent commencer à poster des commentaires sur celui-ci. Cela commence bien, et conduit à la conception suivante :

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

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

Mon contrôleur MVC est conçu comme suit :

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

Maintenant tout fonctionne bien, et cela répond à l'exigence. Dans l'itération suivante, nous avons une exigence selon laquelle chaque fois qu'un commentaire est publié, l'auteur du blog doit recevoir un courriel l'en informant.

À ce stade, j'ai deux choix possibles. 1) Modifier l'article pour exiger un IEmailService (dans le ctor ?) ou obtenir un EmailService à partir d'une référence statique à mon conteneur DI

1a) Cela semble assez laid. Je crois que cela enfreint certaines règles du modèle de domaine dont mes entités connaissent les 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) Cela semble aussi très laid, j'ai maintenant besoin d'un conteneur DI configuré auquel on accède de manière statique.

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) Créez un IArticleService et déplacez la méthode AddComment() vers ce service plutôt que sur l'entité Article elle-même.

Cette solution est plus propre je crois, mais l'ajout d'un commentaire est maintenant moins découvrable et nécessite un ArticleService pour effectuer le travail. Il semble que l'ajout d'un commentaire devrait appartenir à la classe Article elle-même.

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

Je cherche donc essentiellement des conseils auprès de personnes plus expérimentées en matière de modélisation de domaines. Si une solution plus évidente m'échappe, faites-le moi savoir :)

Pour être honnête, je n'aime généralement pas les deux solutions, car l'option Service est moins facile à découvrir. Je ne peux plus ajouter un commentaire à une instance d'un article sans disposer d'un ArticleService. Cela semble également moins naturel, puisque AddComment semble être une méthode tellement évidente sur le type Article.

Quoi qu'il en soit, j'ai hâte de lire vos commentaires. Merci d'avance.

24voto

Mark Seemann Points 102767

Je pense que ce problème particulier peut être résolu de manière élégante avec un Événement de domaine .

2voto

Mikeb Points 3306

Avez-vous envisagé de faire en sorte que le contrôleur d'article transmette essentiellement un message vers le haut / affiche un événement ? Ensuite, tout "article-posted-event-listeners" consommerait ce message et répondrait en conséquence ; dans votre cas spécifique, un notificateur d'e-mail serait à l'écoute de ces événements et serait configuré pour le faire. De cette façon, les bits de publication d'articles n'ont pas besoin de savoir quoi que ce soit sur les bits de notification d'e-mails.

1voto

Rippo Points 10580

L'examen de cette excellente question m'a amené à lire ce qui suit Utilisation du modèle de domaine de Udi sur MSDN.

HTH aide les autres utilisateurs.

J'ai essayé de trouver comment poser cette même question mais j'ai réussi à m'embrouiller plusieurs fois. Votre question ne l'est certainement pas ! Merci

0voto

didibus Points 720

Sans utiliser les événements de domaine, vous pouvez utiliser le modèle de double répartition et placer la logique d'ajout de commentaire dans un service de domaine.

Voici à quoi cela ressemblerait :

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

Si vous préférez, vous pouvez conserver la logique d'ajout de commentaire sur le AddComment de l'article et, à la place, transformer le service de domaine en quelque chose comme ICommentAddedProcessor avec une méthode CommentAdded() et faire en sorte que AddComment de l'article prenne un commentaire et un ICommentAddedProcessor.

0voto

Sudarshan Points 1885

Je pense que chaque fois qu'un expert du domaine utilise le mot "quand" il faut envisager des événements de domaine ou un bus d'événements, c'était un exemple classique dans ce sens.

J'ai écrit un rapport détaillé respuesta qui décrit quand utiliser les bus d'événements, il pourrait être une bonne lecture autour de ce sujet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X