使用DTO和POCO对象时,处理导航属性的最佳做法是什么?

时间:2012-11-13 03:56:14

标签: c# entity-framework poco dto

我正试图围绕领域驱动开发。我想确保我有一个良好的基础和理解它,所以如果在这里避免使用AutoMapper或类似的建议会很好。我的架构目前涉及以下内容:

enter image description here

  • WCF服务负责持久性(使用实体框架)和服务器端验证。它将POCO转换为DTO,并将DTO转移到客户端。

  • 客户端收到DTO并将其转换为POCO。转换POCO和DTO的类在服务和客户端之间共享。

  • POCO的工具IValidatableObjectINotifyPropertyChanged由服务器和客户端使用,但它们不用于数据传输。 DTO是,它们只是不含行为的财产袋。

(1)问题#1。此架构是否适合域驱动设计 (2)问题#2。 POCO是否适合包含导航属性?对于POCO来说,在DDD架构中包含导航属性对我来说确实是错误的,因为对我来说,拥有可能会或可能不会被序列化的导航属性是没有意义的。拥有专业的DTO对我来说更有意义。

例如,这里的POCO / DTO就像我的架构一样。

// Enforces consistency between a POCO and DTO
public interface IExample
{
    Int32 Id { get; set; }
    String Name { get; set; }
}

// POCO
public class Example : IExample, INotifyPropertyChanged, IValidatableObject
{
    private int id;
    private string name;

    public Int32 Id {
        get { return this.id; }
        set {
            this.id = value;
            OnPropertyChanged("Id");
        }
    }

    public String Name {
        get { return this.name; }
        set {
            this.name = value;
            OnPropertyChanged("Name ");
        }
    }

    public ICollection<Example2> ChildExamples {
        get { ... }
        set { ... }
    }

    // INotifyPropertyChanged Members
    // IValidatableObject Members
}

// DTO
public class ExampleInfo : IExample
{
    public Int32 Id { get; set; }
    public String Name { get; set; }
    public ICollection<Example2Info> ChildExamples { get; set; }
}

但它似乎并不正确,因为您可能并不总是需要导航属性,并且在面向对象的体系结构中使用空(null)对象(或集合)似乎非常错误。您还必须有时处理序列化和转换深层对象层次结构,这并非易事。对于专门的DTO来说更有意义,因此不存在可能或可能不需要序列化或填充的空导航属性的可能性问题。

public class ComplexInfo
{
    public Example ExampleInfo { get; set; }
    public ICollection<Example2Info> ChildExamples { get; set; }
}

如何在现实企业DDD样式架构中处理这些情况,以及可以在此处提供哪些其他建议?

4 个答案:

答案 0 :(得分:8)

我同意Jehof关于将DTO发送给您的客户并在您的WCF下保持域模型在服务器端的清洁。

关于导航属性,Eric Evans在Domain Driven Design中强调的一点是尊重不变量。因此,在上面的示例中,问问自己Id和Name是否真的会在对象的生命周期中发生变化,或者它们是不变量?很多DDD风格的开发人员甚至都不会在这些属性上放置一个setter。而是通过构造函数构建对象的不变状态。如果Name可以更改,您可能需要一个名为Rename(string newName)的方法,因为您可能希望在那里放置某种业务规则。

上面图层中的红色标志是您在DAL中拥有整个对象模型。你所说的程序集真的不是什么大问题,但我认为这表明你倾向于从数据的角度思考应用程序。 DDD的要点是根据逻辑和行为来考虑您的对象模型,而不是数据和结构。我(以及大多数其他DDD开发人员,我认为)将数据访问层视为返回Aggregate Roots的Repository类。存储库负责将您的水合poco /实体对象从DAL(存储库)返回到业务层(以及上面的应用程序/服务层类或上例中的WCF)。在使用EF的情况下,您将拥有存储库包装DataContext调用并返回实体对象。

我可以继续,因为你的问题实际上是针对DDD的基本原理,其中有几个。我建议1)阅读Eric Evans的书“Domain Driven Design”。 2)请记住,DDD针对的是复杂的商业软件。如果你试图将它应用到一个简单的CRUD应用程序,它实际上只是UI表单和数据绑定到数据库表,很难看到DDD方法的形成,因为它解决的问题不存在。所以请保持透视。

答案 1 :(得分:1)

领域驱动设计与POCO或DTO无关。它是关于实体,聚合根,价值对象。关于除数据外还可以封装行为的富域对象。

  

POCO适合包含导航属性吗?

我不清楚POCO在您的场景中的用途,但如果它们是您的域实体,那么它们可以并且当然应该包含导航属性。实际上,使用聚合根(一种特殊类型的域实体)的导航属性通常是外部对象访问该聚合中包含的实体的唯一方法。通过关联属性导航是DDD中的一个关键概念。

此外,DDD中推荐的架构看起来或多或少像:

  • 表示层(UI)
  • 申请层
  • 域层
  • 基础架构层(包括持久性/ DAL)

这里的关键是单一责任原则。您不希望服务同时执行持久性,服务器端验证和DTO映射。你需要脱钩。您需要在各层之间明确分配职责,以便更容易维护,扩展和移植。

答案 2 :(得分:1)

  

这种架构是否适合域驱动设计?

不完全。请查看hexagonal architecture,了解更适合DDD的现代建筑风格。在六边形内,您的域名是核心,各种组件都是&#34;附加&#34;它。例如,WCF服务将被视为六边形体系结构中的适配器,因为它使您的域适应TCP或HTTP等通信技术。通常,您将拥有一个应用程序服务,该服务在您的域上建立一个外观并有效地表示用例。 WCF服务可以引用此应用程序服务以通过HTTP公开功能。不幸的是,&#34;服务&#34;术语可能有点混乱。

  

POCO是否适合包含导航属性?

这是合适的,但正确的答案是它取决于。您声明的导航属性的一个问题是它们可能会也可能不会针对特定DTO进行序列化。这告诉我你在谈论查询。某些查询只需要聚合/实体(POCO)上的属性子集,因此相应的DTO只具有那些必需的属性。检索整个实体和导航属性似乎很浪费。要解决此问题,您可以使用延迟加载。然而,更可销售的方法是使用read-models进行查询。此外,正如其他人所说,实体/聚合当然可以而且应该包含导航属性,如果它们是域的反映。这些&#34;导航&#34;实施的属性可以有所不同。有时将聚合分成多个聚合可能更好。看看Effective Aggregate Design by Vaughn Vernon

正如Jehof所指出的,您应该尝试让WCF服务的客户端仅依赖于该服务本身的合同,而不是服务封装的域实体(POCO)。通常,POCO不应实现INotifyPropertyChangedIValidatableObject,因为这些接口支持UI问题,应由DTO或ViewModel处理。

答案 3 :(得分:0)

另一个建议:非常认真地思考是否要在客户端服务器之间共享映射代码(以及暗示它们映射到的类)。 共享代码没有任何问题,但要小心,不要混淆客户端问题和服务器问题。它可能从小的妥协开始“我只需要在客户端上使用此属性,但其他所有内容都相同”,但您最终可能会使用标志来告诉类是否使用客户端或服务器行为以及其他恶意程序。 POCO的单独实现最初可能看起来像代码重复,但它让你有一个适合任务的实现。 这就是为什么使用Automapper等有意义,它降低了编写映射代码的障碍。

执行此操作的另一个原因(也已提到)是DTO应该是实现通信API的一种方式,而不是API本身:即DTO用于WCF实现SOAP API(或REST)或其他),但客户端应该只使用API​​规范自由实现通信层,而映射代码中没有任何隐藏的逻辑。

这也可以确保您的API与语言无关。您可能希望提供客户端库(使用几种适当的语言)来简化与API的交互,但这些不应该是必需的。