10

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

在此处输入图像描述

  • 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

4 回答 4

10

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

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

上面图层中的一个危险信号是您在 DAL 中拥有整个对象模型。您所说的程序集确实没什么大不了的,但我认为这表明您倾向于从数据角度继续思考应用程序。DDD 的重点是根据逻辑和行为而不是数据和结构来考虑您的对象模型。我(以及大多数其他 DDD 开发人员,我认为)将数据访问层视为返回聚合根的存储库类。存储库负责将您的水合 poco/entity 对象从 DAL(repository) 返回到业务层(及更高层,例如上面示例中的应用程序/服务层类或 WCF)。在您使用 EF 的情况下,您将让存储库包装您的 DataContext 调用并返回实体对象。

我可以继续说下去,因为您的问题实际上是针对 DDD 的基本原理,其中有几个。我会推荐 1) 阅读 Eric Evans 的书,“领域驱动设计”。2) 请记住,DDD 针对的是复杂的业务软件。如果您尝试将其应用于一个简单的 CRUD 应用程序,该应用程序实际上只是 UI 表单和与 DB 表的数据绑定,那么很难看到 DDD 方法形成,因为它解决的问题不存在。因此,请保持观点。

于 2012-11-13T14:10:17.227 回答
3

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

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

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

这是合适的,但正确的答案是视情况而定。您陈述的导航属性的问题之一是它们可能会或可能不会针对特定 DTO 进行序列化。这告诉我您正在谈论查询。某些查询只需要聚合/实体 (POCO) 上的属性子集,因此相应的 DTO 仅具有那些必需的属性。检索整个实体和导航属性似乎很浪费。要解决此问题,您可以使用延迟加载。然而,一种更畅销的方法是使用读取模型查询。此外,正如其他人所说,如果实体/聚合是域的反映,它们当然可以并且应该包含导航属性。这些“导航”属性的实现方式可能会有所不同。有时,将聚合拆分为多个聚合可能会更好。看看Vaughn Vernon 的《有效聚合设计》

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

于 2012-11-13T20:34:35.607 回答
1

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

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

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

此外,DDD 中推荐的架构或多或少类似于:

  • 表示层 (UI)
  • 应用层
  • 领域层
  • 基础设施层(包括持久性/DAL)

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

于 2012-11-13T13:48:44.340 回答
0

另一个建议:认真考虑是否在客户端和服务器之间共享映射代码(并暗示它们映射到的类)。共享代码并没有错,但请注意不要将客户端问题和服务器问题混为一谈。它可能从小的妥协开始“我只在客户端上需要这个属性,但其他一切都是一样的”,但你最终可能会用标志告诉类是使用客户端还是服务器行为以及其他讨厌的东西。拥有 POCO 的单独实现一开始可能看起来像代码重复,但它可以让您自由地拥有适合该任务的实现。这就是为什么使用 Automapper 之类的东西是有意义的,它降低了编写映射代码的障碍。

这样做的另一个原因(也已提到)是 DTO 应该是实现通信 API 的一种方式,而不是 API 本身:即 DTO 用于 WCF 实现 SOAP API(或 REST 或其他) ,但客户端应该可以自由地只使用 API 规范来实现通信层,而不需要在映射代码中隐藏任何逻辑。

这也确保了您的 API 与语言无关。您可能希望提供客户端库(使用多种适当语言中的任何一种)以简化与 API 的交互,但这不是必需的。

于 2012-11-13T22:12:19.200 回答