【问题标题】:Distributed DDD in .NET: Sharing Domain Objects with Client.NET 中的分布式 DDD:与客户端共享域对象
【发布时间】:2011-08-16 23:47:49
【问题描述】:

我正在开发一个 3 层应用程序(不是 3 层!),其中一个客户端应用程序运行在一个层(物理集群)上,它与另一个层上运行的服务应用程序和另一个层上的数据库服务器交互。该应用程序有很多业务规则、流程逻辑等,我认为它们应该在应用程序层和服务层都可用,以改善用户体验、减少对服务的调用以及消除冗余编码。

让我们用这个例子:在我的领域层,我有一个 Document 对象。此对象包含一个 AllowPublish 属性,该属性检查对象的内部状态,如果状态允许发布文档,则返回 true/false。该对象还有一个 Publish 方法,该方法通过将 IsPublished 标志设置为 true 并引发 Published 域事件来修改对象的内部状态以反映它正在发布的事实。

我有一个单独的 AuthorizationService 来确定是否允许当前用户发布,还有一个 DocumentRepository 可以将对象持久保存到数据库中。

在我的服务应用程序中,我的 DocumentService 有一个 PublishDocument 方法,该方法接受文档 ID,使用 id 从存储库中检索文档,检查 AllowPublish 属性,如果为真,调用 Publish 然后使用存储库保存更新的对象。

我对客户端的行为略有不同。在这种情况下,我使用 AllowPublish 属性来启用/禁用命令按钮。启用并单击后,我调用了一个服务代理,该代理公开了一个接受文档 ID 的 PublishDocument 方法。代理将调​​用传递给服务应用程序的同名 DocumentService 方法。

为了消除重复代码、共享业务逻辑、验证规则等,我将域对象放在一个单独的程序集中,由客户端应用程序和服务应用程序共享。这意味着客户端应用程序现在可以访问我的 Document 类的 Publish 方法,即使它只是相关的并且只能由我的服务应用程序使用。这让我重新考虑我正在采取的整个方法。

虽然我了解使用 DTO 在客户端和服务器之间传递状态,但我使用的是 .NET 3.5,据我所知,共享程序集是与客户端共享业务和验证规则的唯一方法应用。我有一些想法我可以去哪些其他方向,但希望在开始新的道路之前得到一些建议。

另一方面,我当前为客户实施的方法是我认为的一种迂回授权方法,这可能只是表明不同模型会更好的一个指标。就像我的服务器端服务应用程序中有一个 AuthorizationService 用于 DocumentService 执行授权一样,我有一个类似的代理,我的客户端代码使用它。这意味着我需要在客户端代码中添加另一层间接来支持授权,可能是 Controller 或 ViewModel。如果用例是有效的,那很好。

编辑

我可能需要澄清在编辑文档时 AllowPublish 属性是动态的。当第一次检索时,它可能是假的,但随着业务规则的满足,它会变为真。在客户端应用程序中运行业务规则使我们能够提供更丰富的用户体验。

【问题讨论】:

  • 这可能是研究 CQRS 的好时机。这不是一小步,但从您所描述的情况来看,这可能是一个不错的选择。这个想法是为写入(命令)和读取(查询)建立单独的模型。这样,您的文档对象(又名 DDD 实体或聚合)将成为写入模型的一部分,而在读取模型上,您将拥有一个简单的 DTO,它具有已计算的所有属性 - DocumentDTO 的 AllowPublish 将是一个简单的布尔字段,每当更改 Document 实体时都会更新。如果您需要,我可以提供更多信息,但我认为 google 应该就足够了。
  • 我实际上是在精神上实现 CQRS。例如,检索文档列表的查询方法实际上返回 DocumentInfo 对象,其中包含简单的只读属性(只不过是一个 DTO)。这些 DTO 仍然是从实际的域对象生成的,这些对象具有确定那些布尔属性(如 AllowPublish)将包含什么值的逻辑。虽然我喜欢 CQRS 背后的概念,但我不喜欢该实现,因为我发现它超出了 avg 开发人员的理解范围。因此,我尝试使用 R/O DTO 进行读取和使用域对象进行写入来保持简单。
  • 我应该澄清一下:CQRS 失去了我,因为它是一个事务存储——这意味着我们存储的是事务,而不是状态。这对许多应用程序来说都很好,但我在努力中还没有遇到一个非常合适的案例。我不想爬过交易列表来查找我当前的状态,并且为汇总数据等实施服务所带来的额外开销太大了。我喜欢分离职责的想法,并且实际上已经实现了一个版本,其中查询是针对 CUBE 而不是实际的数据存储执行的。就在我来的时候。
  • 我认为您是在暗示 CQRS 需要 EventSourcing。只是为了澄清 CQRS 并不意味着 EventSourcing。 CQRS 仅意味着使用域事件同步的命令和查询的单独模型。保留模型的方式取决于您。您不必使用 EventSourcing,但我鼓励您观看 Greg Young 的视频,作为 ES 的精彩演示。

标签: .net domain-driven-design soa


【解决方案1】:

为了结束和将来看到这篇文章的任何人,我想我会分享我最终得到的结果,同时感谢 Iulian 帮助我朝着正确的方向前进。

简单地说,我意识到(在 Iulian 的帮助下)我确实在客户端应用程序和服务器端服务应用程序之间有两个不同的用例。结果,我咬紧牙关,为每个人创建了单独的域模型。

我的部分思考过程是在逻辑上分离应用程序本身。虽然客户端应用程序无法在没有服务应用程序的情况下运行,但我调整了思路,将这种关系更多地视为数据访问层,而不是应用程序和域层。同时,我改变了对服务器端的看法,将服务接口视为该应用程序的表示层。因此,拥有不同的域对象/层非常有意义。

不幸的是,这确实需要权衡取舍。我希望在我们推进技术以利用 RIA 服务时尽量减少这一点,也许这将使我们能够将数据注释从服务器传递到客户端。不过,目前,我使用简单的 DTO 对象在应用程序和将 API 提供到核心域逻辑的 RESTful 服务接口之间传递状态信息。

使用我最初引用的示例,我有以下设置:

当用户单击 UI 中的“发布”按钮时,客户端应用程序会调用我的服务代理类上的 Publish 方法。服务代理处理与服务器端服务的通信。在这种情况下,DocumentService 公开了一个 Publish 方法,该方法接受要发布的 Document 的 ID(以及用户信息等)

DocumentService 从 DocumentRepository 中检索 Document 域对象,并在该对象上调用 Publish 方法来更新 Document 的内部状态。然后服务调用 DocumentRepository 上的 Update 方法,传入更新后的 Document 对象,并将更改持久化到数据库中。

权衡是我需要有逻辑/规则来确定是否以及何时可以在客户端和服务器上发布文档(因为我们不能假设请求始终有效)。同样,将解决方案视为两个独立的应用程序,并拥有自己的一组用例,这有助于使这更合理(在我看来)。如您所见,我不需要在 Document 的客户端版本中使用 Publish 方法,但我确实需要更改跟踪以获得丰富的用户体验。我不需要在服务器上进行相同类型的更改跟踪,因为对象更改时没有 UI 刷新。在后一种情况下,更改跟踪由 ORM 实现,以使持久性更加优化。

因此,对我来说,底线是将解决方案分成不同的应用程序,这使我能够隔离用例并提取每个应用程序所需的适当对象和关系以满足其目的。现在我有了一个也支持多个客户端的解决方案,因为我已经将客户端与服务器解耦,这种方式允许我插入新客户端,而无需更改服务应用程序或现有客户端应用程序。

HTH

【讨论】:

    【解决方案2】:

    您不应将域模型对象放在客户端中。将它们直接用于客户端将限制您在未来迭代中发展领域的能力,并且在进行 DDD 时,当您从领域专家那里获得更深入的见解时,发展您的领域的能力至关重要。

    我不知道这在您的情况下是否可行,但也许您可以将业务规则分解为一些策略对象,它们只会具有非常具体的行为,可以在域模型和客户端中使用.如果您的目标是避免逻辑重复并且如果您需要的行为完全相同 - 这可能不是这种情况。在您的客户端中,您可能需要一些额外的验证步骤,这些步骤可能与您在域模型中所需的步骤不同。

    当您可以在 ViewModel 中进行客户端验证时,最好的解决方案可能是使用 MVC 或 MVVM 模式,如果可能的话,可以基于一些共享规则。

    我来宾的主要思想是不要为了 DRY 而结合概念。像往常一样,Udi Dahan 写了一篇关于此的文章:The Fallacy Of ReUse

    【讨论】:

    • 对于 Udi Dahan 的文章的引用,我会 +1 你,但我不同意不应在客户端中使用域模型对象的想法。在某些系统中,允许域可在本地访问以减少调用服务器以执行简单的操作(例如“请验证此单个字段?”)更有意义。或“根据特定输入计算新的最终价格”。
    • 与软件开发中的任何事情一样,没有黑白之分。作为指导原则,您不应在客户端中使用域模型对象。在实践中,我确信在某些情况下在客户端中使用域模型对象是可以接受的 - 但只有在您了解采用这条路径的全部含义时,您才应该这样做。
    • 客户端中没有域模型的原因是什么?域的安全性/完整性?我可能是错的,但我希望在域仅驻留在客户端中的情况下存在许多应用程序。除非我们谈论的是 Web 应用程序,否则我看不到将域设为服务器或 N 层体系结构中的中间层的好处。不过会对你的想法感兴趣。
    • 在我看来,客户端就是 UI,它可以是任何东西,从 Web 到本机,再到消耗应用程序公开的功能的任何东西。将域模型直接放在客户端中会在两者之间产生非常紧密的耦合。在某些情况下,这可能是可以接受的,但在大多数情况下,它会降低您发展领域模型的能力,因为您需要为您对模型所做的每次更改更新客户端。另一个原因可能是重复。如果您有使用相同服务的 Web 客户端和本机客户端,则您不想在两者中重复域逻辑。
    • 好的,好点。如果您的应用程序是一项开放服务或必须与各种客户端互操作的应用程序,那么是的,我明白您的意思。此外,如果从服务器更新客户端并要求它们进行版本同步不是一种选择,那么在这些情况下共享域将是灾难的根源。我想我一直在研究和开发的应用程序通常属于 n 层业务应用程序类别,其中只有一个客户端与服务器版本匹配,因此共享域很有意义。跨度>
    【解决方案3】:

    考虑使用InternalsVisibleTo 属性。

    【讨论】:

    • 这是我正在考虑的选项之一,但我通常保留它以授予单元测试访问权限。我觉得这可能会使代码有点“臭”。但是,这是我想到的一个考虑因素。
    猜你喜欢
    • 2015-10-14
    • 2011-03-29
    • 2011-03-19
    • 1970-01-01
    • 2018-07-21
    • 2019-04-02
    • 1970-01-01
    • 1970-01-01
    • 2011-01-30
    相关资源
    最近更新 更多