如何避免贫血域模型,或者何时将方法从实体转移到服务中
我有一个共同的情况,我正在寻找一些更有经验的DDD和领域build模经验的人的指导。
假设我开始构build一个博客引擎,第一个要求是文章发布后,用户可以开始发布评论。 这开始很好,并导致以下devise:
public class Article { public int Id { get; set; } public void AddComment(Comment comment) { // Add Comment } }
我的MVC控制器是这样devise的:
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"); } }
现在一切正常,符合要求。 下一次迭代,我们得到一个要求,每次发布评论时,博客作者都应该收到一封电子邮件通知他。
在这一点上,我有两个select,我可以想到的。 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)也似乎难看,我现在需要一个静态访问configurationDI容器。
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)创build一个IArticleService,并将AddComment()方法移到这个服务上,而不是在文章实体本身上。
我相信这个解决scheme更清洁,但添加评论现在不易发现,需要一个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"); } }
所以我基本上是从领域build模经验丰富的人那里寻求build议。 如果我错过了一个更明显的解决scheme,请让我知道:)
我一般都不喜欢这两种解决scheme,因为服务选项不易发现。 如果没有可用的ArticleService,我不能再添加Comment到一个Article的实例。 它也感觉不那么自然,因为AddComment看起来像是一个明显的文章types的方法。
无论如何,我期待着阅读input。 提前致谢。
我认为这个特殊的问题可以通过域名事件来解决。
你有没有考虑过文章控制器本质上是传递一个消息/发布一个事件? 然后,任何“文章发布事件监听器”将消耗该消息并作出相应的响应; 在您的具体情况下,电子邮件通知程序将监听这些事件并进行configuration。 这样,文章发布位不需要知道任何关于电子邮件通知位的信息。
通过这个优秀的问题,让我在MSDN上阅读从Udi 使用域模型模式 。
HTH帮助其他用户。
我一直在努力研究如何提出这个问题,但却多次让我感到困惑。 你的问题当然不是! 谢谢
如果不使用域事件,可以使用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条上保留Add Comment逻辑,而是使用CommentAdded()方法将域服务变成类似ICommentAddedProcessor的东西,并且在文章上添加注释以及ICommentAddedProcessor。
我认为,只要领域专家使用“什么时候”这个词,就必须考虑域名事件或事件总线,这是一个典型的例子。
我已经写了一个详细的答案 ,描述了什么时候使用事件总线,这可能是一个很好的解读这个话题
- 在Rails中是否存在dependency injection?
- 在VS2015的UWP解决scheme文件夹中创build奇怪的子文件夹“DTAR_08E86330_4835_4B5C_9E5A_61F37AE1A077_DTAR”