放弃DDD,但需要一些好处

时间:2012-10-19 21:26:08

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

我放弃传统的DDD,这通常是一个庞大的时间,并迫使我做无尽的映射:data layer <--> domain layer <--> presentation layer

即使是一个小小的改变,我必须改变数据模型,领域模型,表示模型/视图模型,然后是存储库,经理/服务类,当然还有AutoMapper映射,然后测试整个事情!每次调用都需要调用一个层,该层调用一个调用底层代码的层。 除了“你将来可能需要它”之外,我得不到任何回报。。 MEH。

我目前的做法更务实:

  • 我不再担心“数据层”和“域层”之间的区别,因为没有意义 - 这些术语是可以互换的。我让EF做它的事情,并在需要时在顶部添加接口和存储库。
  • 我已经合并了我的“数据”和“域”项目(进入“核心”,无聊的名字,我知道),我几乎可以发誓Visual Studio实际上运行得更快。
  • 我允许EF实体在堆栈中上下移动,但是,我仍像往常一样将它们映射到表示模型/视图模型。
  • 对于简单的操作,我直接从控制器调用存储库,对于复杂的操作,我像往常一样使用域管理器/服务;存储库永远不会暴露IQueryable。
  • 我将实体/ POCO定义为部分类,因此我可以在相应的部分类中单独添加域行为。

问题:我现在在整个地方使用实体,因此客户端代码可以看到他们的导航属性。并且模型在离开存储库后始终具体化,因此这些导航属性通常为空。

可能的解决方案:
1.和它一起生活。它很丑,但比上面解释的问题更可取 2.对于每个实体,定义一个隐藏导航属性的接口;并使客户端代码使用接口。但具有讽刺意味的是,这意味着另一层(尽管很薄且易于管理) 还有什么?

我不习惯这种快速松散的编程风格,所以也许我错过了一些明显的技巧。还有什么我应该考虑的吗?我相信我很快就会遇到其他问题。

修改 这个问题与DDD无关。请注意,很多人都采用传统的DDD方法 - Seemann appears to到达the same conclusionRahien speaks about the "Useless Abstraction For The Sake Of Abstraction Anti Pattern",而Evans本人表示DDD仅在5%的情况下才真正有用。 see this thread也是{{3}}。一些评论/答案可以预见我将如何做DDD错误,或者我如何调整我的系统来做正确的事。但是,我并不是要问DDD还是在适合的情况下抨击它,而是我想知道其他人在做什么符合我上面所描述的思路。这并不是说DDD是所有设计弊病的灵丹妙药,每十年都有一个新的流程出现(RUP任何人?XP,Agile,Booch,等等......)。 DDD是最新的,也是最知名和最常用的。但实用主义应该是第一位的,因为我正在努力建造能够按时发货且易于维护的可销售产品。我学到的最有用的编程公理到目前为止是YAGNI。 我想要的是将我的系统改为某种“DDD-lite”,在这里我得到了强大的设计/ OOP /模式理念,但没有脂肪。

5 个答案:

答案 0 :(得分:5)

DDD的典型持久性方法是将域模型直接映射到相应的表。从技术上讲,映射仍然存在(通常在代码中声明),但没有明确的数据模型,正如 lazyberezovsky 所指出的那样。

无论您是否使用DDD,都可以通过几种不同的方式解决导航属性的问题。我不喜欢方法1,因为它使你的代码更难以推理 - 你永远不知道将设置哪些属性,哪些不会。方法2在理论上要好得多,因为它使得非常显式给定查询所需要的东西并使事物明确是一般的好习惯。类似但更简单且不太脆弱的方法是使用read-models,它们只是旨在满足给定查询集查询要求的对象。在DDD的上下文中,它们允许您将行为丰富的实体与查询分离,这些查询通常存在争议。现在DRY的支持者可能尖叫异端并用火炬和干草叉向你走来,但实际上,维护读模型和实体然后试图通过强制实体强制实现查询要求通常要容易得多。接口或复杂的映射策略。此外,阅读模型和行为模型的职责完全不同,因此DRY不适用。

这并不是说DDD适用于您的场景。避免完全成熟的DDD通常是明智的决定,尤其是在大多数CRUD的情况下。谨慎是正确的,KISS and YAGNI的一个很好的例子。当您的域包含复杂的行为而不仅仅是数据时,DDD会获益。无论如何,读取模型模式都适用。

<强>更新

对于不使用读取模型的实现,请查看Fetching Strategy Design,其中获取策略的概念允许从数据库中准确指定所需的内容,从而减轻导航属性的问题。链接帖子中引用的材料也很有用。总的来说,这试图避免其他方法中存在的一层间接。但是,在我看来,使用提议的提取策略比使用读取模型更复杂,而最终结果是相同的。

答案 1 :(得分:2)

关于这一点的一些想法:

  

......存储库永远不会暴露出IQueryable ......模型总是如此   他们离开存储库后实现了......

您的问题标有&#34; asp.net-mvc&#34;,因此您需要考虑Web应用程序。所有请求的90%或更多将是GET请求,这些请求应该从数据库中获取一些数据并在Web视图中显示这些数据。这些所需数据通常是实体的频率,而不仅仅是一堆属性(实体类型的属性选择,或者可能由多个实体的属性组成)?

说,您的应用程序有100个视图。只有少数这些将显示完整的实体:

  • 其中50个是显示所选数据的列表视图(具有ID和地址的客户,但没有客户的联系人,电话号码和销售量)
  • 其中20个包含自动填充文本框以选择参考(订单的客户,但只有客户的姓名和城市显示在自动填充列表中,而不是地址的其余部分或联系人,电话数量和销售量,只显示前5个点击)
  • 1是显示所有内容但不显示销售量的客户的编辑视图
  • 1是具有最近五个订单的客户的详细信息视图
  • 1是订单的详细信息视图,其中包括订单商品,包括每件商品的产品但没有商品的供应商名称
  • 1是相同的视图,但专门针对希望查看供应商的每个项目和项目产品的采购部门,其中包含过去三个月平均供应商的提前期。
  • 1是服务部门的视图,显示仅包含产品类别的订单商品的订单&#34;维修服务&#34;
  • 人力资源部门的1个视图显示员工包括存储为大blob的照片
  • 1人事规划部门的视图显示了没有照片的员工的简短版本
  • 等等。

作为一名UI程序员,我会有各种各样的数据要求来呈现上面的例子:

  • 我只需要选择一些属性
  • 我甚至需要为不同的视图选择相同实体的属性
  • 我需要包含所有商品但没有产品参考的订单
  • 我需要一份订单,包括所有商品(但不是商品的所有属性),包括对产品和供应商的引用(但不是所有供应商的属性)
  • 我需要的订单只包含已过滤的订单商品列表
  • 我需要一个客户,包括最后五个订单,而不是他曾经拥有的所有3000个订单
  • 我需要一名员工,但请没有大blob图片
  • 等等。

如何作为数据访问/存储库/服务开发人员满足这些要求?

  • 我只提供了一些方法和物化实体:加载订单标题,带项目的加载订单标题,带项目和产品的加载订单标题,带项目和产品和供应商的加载订单标题,加载客户标题(抛出15个20个属性,亲爱的UI开发人员,如果你只需要五个属性),加载所有3000个订单的客户头(抛出2995,亲爱的UI开发人员,如果你只需要五个),等等,我从存储库返回接口隐藏未加载的导航属性。
  • 我关心UI需要的每个细节:我创建存储库/服务方法,如GetFiveCustomerPropertiesForAutoCompleteGetCustomerWithLastFiveOrders等。我从隐藏属性的存储库返回接口(也是标量)我还没装好。或者我返回&#34; DTOs&#34;包含请求的属性。当UI开发人员调用下一个视图的数据要求时,我每天都会更改存储库/服务并创建新的DTO。
  • 我从存储库返回IQueryable<TEntity>并告诉UI开发人员&#34;自己创建LINQ查询以获取视图所需的数据&#34;。 (第二天早上,DBA抱怨数百个糟糕的数据库查询。)
  • 我回来准备&#34; IQueryable<TEntity>来自存储库/服务,其中包括 - 例如 - 安全问题,例如为用户的访问权限应用Where子句或为搜索字词附加Where子句或对查询应用NoTracking选项。我告诉UI开发人员:&#34;您可以使用a)投影(Select),b)分页(TakeSkip)以及c)排序来扩展查询(OrderBy)因为我将这三个查询部分视为UI问题。所有其他查询要求(过滤,加入,分组等)必须在存储库/服务层中实现,并且在UI层中被禁止。&#34;这里最重要的部分是通过LINQ / SQL查询直接实现ViewModels的投影,没有中间映射层,并且没有超出所需列/属性的开销。

这些只是一些想法。每种方法都有其优点和缺点。在小型团队中工作,其中至少有一个或几个开发人员可以概述存储库/服务和UI /&#34;投影&#34;图层最后一个选项在我的经验中对我来说很好,虽然它并不总是符合所描述的严格规则(例如,按订单的已包含订单项按产品类别过滤需要应用Where投影内部的子句,即在UI层中。对于POST请求和数据修改,我会使用DTO将从视图中收集的数据发送回要在那里处理的服务。

更严格地分离&#34;查询层&#34;和UI层我可能更喜欢接近第二个选项的东西,可能没有针对每个UI要求的接口/ DTO,但是为了最常见的要求,某种程度上减少了一组DTO(有时候不必要的一点开销的价格)加载属性)。但是,由于大量必要的存储库/服务方法,(可能很多)DTO的额外维护以及DTO和ViewModel之间的中间映射,我希望这比最后一个选项更多。

当我90%的时间不需要它时,我个人担心实现完整的实体,特别是复杂的对象图。但我的担忧并没有得到广泛的性能测量的证实,证明这种方法对于正常情况来说确实是一个问题。应用程序没有特殊的高性能需求。

答案 2 :(得分:1)

当我们不知道你正在建造什么时,怎么能给你合理的建议呢?在宏伟的计划中,你可能正在构建错误的解决方案(不是说你是)。因此,要意识到我们所能涉及的是技术设计问题和类似的过去经历。

确实,很多人都面临着你的问题。映射是静态类型的松散耦合税。也许更动态的语言可以解决你的一些痛苦。或许你可能会在自动化更多(DSL,MDA)方面找到优势。您也可以切换到客户端服务器。

接口不是层,而是抽象。明智地使用它们。

就个人而言,我永远不会采取这些捷径。被咬了太多次试图跳过步骤。逻辑开始在奇怪的地方出现。如果我有一个数据驱动的应用程序来开发简单的数据集,我也会想到。但是我没有在DDD意义上调用对象聚合或实体,只是ERD意义上的实体。 Transactions可能比部分方法洒水更合适。对于读取模型对象,这些不是间接层。

总的来说,我得到了这种感觉,就是这样,你制造了一堆乱七八糟的东西,因为你通过依赖于不显示所需形状的对象来对抗映射摩擦(导航属性是因此导致不同领域的问题。

答案 3 :(得分:0)

我只是试着做一下 - 我们去了方法2 - 即添加你在客户端上使用的接口层。你可以让EF为你生成它们,只需稍微调整一下.tt模板。

是的,它创建了(还)另一个层,但它没有逻辑,并没有增加复杂性。当然,如果您的客户端需要反序列化实体,您必须添加(还)另一个将处理反序列化的层,并引用实体定义和他将返回给客户端的接口。但它也很薄,所以我们学会了忍受它,因为它结果很好,客户真的保持干净......

答案 4 :(得分:0)

  

问题:我现在使用遍布各处的实体,所以客户端代码   可以看到他们的导航属性。

我不明白为什么这是一个问题,特别是它与EF实体有什么关系。通过客户端代码,您是指表示层代码还是任何消耗您实体的代码?

对于UI代码,一个简单的解决方案是定义ViewModel,它们不会暴露这些导航属性(或者只根据GUI需要的对象图深度公开其中的一些)。

对于其他代码,能够看到实体的导航属性是正常的。他们是公开的有一个原因。如果你滥用德米特法,你最终可能违反了德米特法则,但这不是开发人员的纪律,而是陷入陷阱。

实体包含自己的合同 - 所有可以访问该实体的代码都应该能够使用此合同的任何部分。如果你觉得你的实体暴露太多,并且你需要在它们之上放置接口以限制对某些部分的访问,那么它可能只是一个不同的实体。

  
      
  • 我不担心&#34;数据层&#34;和&#34;域层&#34;因为没有意义 - 条款是
      互换。我让EF做它的事情,并添加接口和
      需要时,存储库位于顶部。
  •   
  • 我合并了我的数据&#34;和&#34;域名&#34;项目(进入&#34;核心&#34;,无聊的名字,我知道),我几乎可以发誓Visual Studio是   实际上跑得更快。
  •   
  • 我允许EF实体在堆栈中上下移动,但是,我仍像往常一样将它们映射到演示模型/视图模型。
  •   
  • 对于简单的操作,我直接从控制器调用存储库,对于复杂的操作,我使用域管理器/服务作为   通常;存储库永远不会暴露出IQueryable。
  •   
  • 我将实体/ POCO定义为部分类,因此我可以在相应的部分类中单独添加域行为。
  •   

对我来说,这些事情似乎都不是根本的反DDD,除了数据/域名分离。

特别是如果你做数据库优先EF -DDD显然是一种以域为中心的方法,在定义实体之前你不应该定义你的表。它还不清楚你的某些域实体是直接与数据库或EF通信(不是DDD - 更一般地说,是分层架构兼容的),或者你系统地在它们之间有数据访问对象(DDD兼容)。 / p>