【问题标题】:DDD - communication between the presentation and domain layerDDD - 表示层和域层之间的通信
【发布时间】:2014-02-17 14:05:58
【问题描述】:

我无法将我读过的许多帖子中的点联系起来(并且意识到我可能需要购买 Eric Evans 的书,而不是依赖大量的互联网资料)我正在努力坚持领域驱动设计,但我在将我的数据保存回数据库之前,我在从表示层到我的域的最佳通信方法上遇到了麻烦。为了简单起见,现在我有一个用户类,它可以有一个工作列表。当用户打开应用程序时,他们的活动目录信息被查询,并且只要他们打开应用程序,这个类就会填充他们的数据。然后,用户可以编辑现有工作(如果他们拥有该工作)或创建新工作。我开始创建一个使用我的 UnitOfWork 类的服务类,认为这可以作为我的通信,但这就是我被困的地方。

当前设置:

DAL - EF 6 生成的 POCO 和 DbContext、存储库、工作单元

域 - 我在这篇文章中提到的实体、存储库和工作单元接口、域接口服务?

Presentation - MVC(Intranet),具体服务?

问题:

  1. 对于这种类型的通信(以及创建域类的新实例(例如创建新作业的方法)),服务类是实现的最佳类吗?我意识到我可以使用工厂模式,但我还不想变得太复杂)?服务会驻留在域层还是我为应用层创建一个单独的项目?那么接口会进入领域层,具体实现会进入应用层吗?我觉得这可能会使应用程序过于复杂。另外,服务是 WCF 还是我可以创建自己的类?

  2. 如何使用此服务(如果这是最好的方法)将 MVC 层中的 ViewModel 映射回域,而不会使表示层泄漏到服务中?我已经阅读了有关 DTO 的内容,但是在服务层中这是否有点过头了?还是可以将我的域实体公开为 ViewModel?我一定是在错误地思考这个问题。我无法想象这种交互在没有泄漏的情况下在控制器中的外观。抱歉所有问题,谢谢。

    public class User
    {
        public int Id{ get; set; }
        public string WindowsId{ get; private set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string FullName { get { return FirstName + " " + LastName; } }
        public string Email { get; set; }
        public string WorkPhone { get; set; }
    
        public List<Job> Jobs { get; private set; }
    
        public void AddJob(Job job)
        {
            if(job == null)
            throw new Exception("Job is null");
    
            var newJob = new Job(this) //tell the job that this user is creating the job
            {
                Description = job.Description,
                DueDate = job.DueDate,
                Name = job.Name,
                Reason = job.Reason
            };
    
        Jobs.Add(newJob);
        }
    
    }
    

更新:我的解决方案

我最终从每个人的答案中提取了一点。

@granadaCoder - 我使用我的 EF POCO 作为类作为 DTO 传递,因为大多数时候这是我在 ViewModel 中向用户显示的数据。所以它允许我使用 DTO 作为 ViewModel

@ChrisPratt - 你是对的。这样做可以避免很多额外的工作。我所做的只是创建一个包含我需要的所有查询的服务。如果我不得不更改 EF,它仍然不会打扰我的其他层。我所要做的就是创建存储库和 UOW

@MilivojMilani - 我确实在控制器中有重复逻辑,因此服务层让我与 DRY 主体保持一致

@Yorro - 我使用你的观点和参考来加强设计,因为我仍然不确定服务层设置。由于这是一个较小的项目,我用我创建的服务和界面设置了另一个文件夹,没有 WCF

【问题讨论】:

  • 我认为这是与架构相关的问题,而不是与 DDD 相关的问题。您看过“端口和适配器”架构吗?它可能会给你一些方向和答案。 alistair.cockburn.us/Hexagonal+architecture
  • 我喜欢本教程的地方在于作者对“已连接”与“未连接”的关系非常明确......也许它会有所帮助。 entityframeworktutorial.net/…
  • 我认为您的重大决定是您是否通过网络传递 EF-POCO 或您自己的 POCO。我已经尝试让 EF 填充 EF-POCO,然后将它们转换为 My-POCO。并通过网络发送那些。变得困难的是,当您将它们传回时,必须重新连接对象模型的层次结构(MyPoco 到 EF-Poco)并让 EF 正确更新所有内容。这是一个 NHibernate 参数...有点映射到 EF:thatextramile.be/blog/2010/05/…
  • @granadaCoder,这样的图表是否可以工作:存储库 -> 实体
  • 我不知道我们是否说同一种语言,但我有:Repository(Pattern) --> MyPocoDAL --> EFPocoTranslater --> EF --> EFPoco's -- > MyPoco 的

标签: c# asp.net-mvc entity-framework domain-driven-design


【解决方案1】:

如果您使用的是实体框架,请远离存储库/工作单元模式。 EF 本身就是这种模式的实现。如果你想抽象实体框架,那么实现一个服务模式,它会按照你的需要返回完全烘焙的数据。最好用一个例子来说明:

使用 EF/存储库

IQueryable<Post> posts = db.Posts.Where(m => m.BlogId == blog.Id && m.Status == PostStatuses.Published && m.PublishDate <= DateTime.Now).OrderBy(m => m.PublishDate);

有服务

List<Post> posts = service.GetPublishedPostsForBlog(blog);

这里的两个主要区别是:

  1. 您正在返回已经使用服务从数据库中提取的数据;存储库/EF 将返回一个可能已经或可能尚未针对数据库执行的可查询对象。

  2. 您的所有逻辑都进入带有服务的服务方法。然而,使用存储库/EF,您可以就地构建查询。

也就是说,不要太拘泥于应用程序的各个层。这不是关于你有多少层以及你如何称呼它们,而是关于从你的应用程序中抽象出不需要知道如何构建逻辑的部分的逻辑。例如,返回已发布博客帖子列表的控制器操作不需要知道什么使帖子符合“已发布”的条件。那是应该去其他地方的域逻辑(比如返回这个数据集的服务方法)。这个想法很简单,就是遵循单一职责原则:一个类/方法/等。应该做one的事情并且把它做好。您的控制器操作应该只关心返回视图(并做最少量的工作来获取该视图所需的内容)。

就映射而言,该术语准确地解释了您所做的事情,所以我不确定这里的混淆。如果你想将Post 映射到PostViewModel 例如:

var model = new PostViewModel
{
    Title = post.Title,
    Content = post.Content,
    ...
}

或者使用对象列表,您可以使用 LINQ:

var model = posts.Select(m => new PostViewModel
{
    Title = m.Title,
    Content = m.Content,
    ...
}

如果您的问题很简单:我该怎么做更容易?然后,您可以查看 AutoMapper 之类的第三方映射库。

【讨论】:

  • 我删除了我之前的评论,因为我认为我有更好的理解。如果我错了,请纠正我。您将上下文放入服务中,这就是您与数据交互的方式?那么你在领域层有一个抽象而具体的服务,并将服务接口传入控制器?当您必须创建类的新实例(例如插入记录)时,您的服务如何处理从视图模型中获取数据?
  • 我想我仍然对这个例子感到困惑。您将与 EF 交互的方法的服务放在哪里?把它放在你的域层是不好的,因为你会泄漏 ORM。这就是我在我的域中创建接口存储库并在 DAL 中创建具体存储库的最初原因。这样我就可以在我的域中拥有一个使用 IRepository 的具体服务
  • ORM 只是一个存储库。仅仅拥有一个控制器使用的服务就足够抽象了。抽象是好的,但你不想一层一层地添加它;这只会让你的代码更难维护。
  • 所以你是说把这个具体的服务放在应用服务层,我的EF模型会留在它自己的DAL层,而服务接口在我的业务层?
  • 我不确定这是否是一个失败,因为如果我摆脱了 EF,这将是一个简单的解决方案,但我意识到如果我将服务放在另一个层中,我需要实体框架作为一个参考,所以我的 DAL 和服务层中有 EF。
【解决方案2】:

服务层

服务层充当应用程序的边界,它封装了您的域实体,换句话说,它保护了您的域。与域的所有通信都必须通过服务层。但是域模型(最纯粹的形式)不应该对服务层或任何其他基础设施层(DAL/Presentation)有任何引用。

http://martinfowler.com/eaaCatalog/serviceLayer.html

服务是驻留在领域层还是我为应用层创建一个单独的项目?那么接口会不会进领域层,具体实现会不会进应用层呢?

如果您已经将层分离到自己的项目中,那么服务层应该有自己的项目。

服务是 WCF 还是我可以创建自己的类?

最简单的服务只是一个类,直接从 Microsoft 教程中查看服务层示例: http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs

这是另一个关于如何使用服务层重构 MVC 的示例 http://www.arrangeactassert.com/asp-net-mvc-controller-best-practices-%E2%80%93-skinny-controllers/

如何使用此服务将 MVC 层中的 ViewModel 映射回域(如果这是最好的方法),而不会使表示层泄漏到服务中?我已经阅读了有关 DTO 的内容,但是在服务层中这是否有点过头了?还是可以将我的域实体公开为 ViewModel?我一定是在错误地思考这个问题。我无法想象这种交互在没有泄漏的情况下在控制器中的外观。

这个问题完全是另一个话题。 DTO 是服务层与外部层通信的好方法。

您的问题非常好,而且实际上是最常见的问题。

  • 在服务层使用 DTO 是否过大?
  • 可以将我的应用程序的核心(域)暴露给外部层吗?
  • 服务层如何与外部层交互而不发生泄漏?
  • DTO 应该驻留在哪里?

我在这里回答了同样的问题:https://stackoverflow.com/a/21569720/1027250

This image 提供服务层如何适应多层架构的俯视图。请注意,这对于每个项目来说都不是绝对的或完美的,您应该根据项目的需要采用一种模式。附加层会增加复杂性,这是对大型团队的长期项目的长期投资。

【讨论】:

  • 很好的答案,也感谢您的链接。在发布这个问题之前不知道我是如何错过的,但每个人的回答都帮助了我。我将在我的问题中发布我最终最终做了什么的更新。请让我知道是否有什么不妥之处,因为您似乎在建筑方面拥有丰富的经验。
【解决方案3】:

1.如果您使用 MVC,您可能有也可能没有服务层。问自己以下问题 - 您是否在多个地方使用此功能?如果答案是肯定的 - 使用 application 服务(不是域服务)。如果不是,则将其放入 MVC 控制器本身。

不要在域层中放置任何水合逻辑。 DDD 中的领域层不应该对持久性有任何了解。控制器/应用程序服务应该使用存储库接口获取数据并使用相同的接口保存数据。在您的情况下,您应该只获取和保存 User 类,而 Job 应该在它下面作为聚合根子级。使用 UnitOfWork 和 Repository 非常好。这是一篇很好的文章=>

http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

2.在某些情况下,使用域对象作为 ViewModel 类是完全正常的。在其他更复杂的情况下,您将不得不手动映射属性。映射通常最好作为 ViewModel 类的扩展方法来完成。有些人使用 AutoMapper,尽管我反对在将表示类映射到域时使用它。所有这些映射工作都不应该在域层中完成。您可能有很多视图模型类,并且它会使表示层逻辑膨胀的域层。

希望对您有所帮助。

【讨论】:

    猜你喜欢
    • 2015-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    • 2020-07-20
    • 1970-01-01
    • 2022-10-17
    相关资源
    最近更新 更多