2011-05-02 70 views
4

我正在开发一个3层应用程序(不是3层!),其中一个层(物理群集)上运行的客户端应用程序与另一个服务应用程序层和另一层上的数据库服务器。应用程序有很多业务规则,流程逻辑等,我相信应该可以在应用程序层和服务层上使用,以改善用户体验,减少对服务的调用并消除冗余编码。.NET中的分布式DDD:与客户端共享域对象

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

我有一个单独的AuthorizationService,它确定是否允许当前用户发布以及将该对象持久化到数据库的DocumentRepository。

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

我在客户端上的行为稍有不同。在这种情况下,我使用AllowPublish属性来启用/禁用命令按钮。启用并单击后,我会调用一个服务代理,该服务代理公开一个接受文档ID的PublishDocument方法。代理将该调用传递到具有相同名称的服务应用程序的DocumentService方法。为了消除重复的代码,共享业务逻辑,验证规则等,我将域对象放置在一个单独的程序集中,由客户端应用程序和服务应用程序共享。这意味着客户端应用程序现在可以访问我的Document类的Publish方法,尽管它只与我的服务应用程序有关,并且只应该被我的服务应用程序使用。这让我重新考虑了我正在采取的整个方法。

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

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

EDIT

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

+2

这可能是调查CQRS的好时机。这不是一小步,而是根据你所描述的,这可能是一个很好的选择。这个想法是为写入(命令)和读取(查询)分别建立模型。这样,您的文档对象(又名DDD实体或Agregate)将成为写模型的一部分,在读模型中,您将拥有一个简单的DTO,其中包含所有已经计算好的属性 - DocumentDTO的AllowPublish将成为一个简单的布尔字段每当Document实体发生更改时都会更新。如果您需要我可以提供更多信息,但我认为谷歌应该足够了。 – 2011-05-02 14:18:12

+0

事实上,我的精神实施CQRS。例如,检索文档列表的查询方法实际上会返回包含简单只读属性(不过是DTO)的DocumentInfo对象。这些DTO仍然由具有逻辑的实际域对象生成,以确定像AllowPublish这样的布尔属性将包含哪些值。虽然我喜欢CQRS背后的概念,但我并不喜欢实现,因为我发现它超出了平均开发人员的理解范围。所以,我尝试用R/O DTOs来简化读取和写入域对象。 – SonOfPirate 2011-05-02 15:34:13

+0

我应该澄清:CQRS因为它是一个交易存储而失去了我 - 这意味着我们正在存储交易,而不是状态。这对许多应用程序来说都很好,但对于我的努力,我还没有遇到过这样的情况,因为它非常适合。我不想通过一系列交易来检索我的当前状态,并且实施服务对于汇总数据的额外开销等等太多。我喜欢分离职责的想法,实际上已经实现了一个版本,其中查询是针对CUBE而不是实际的数据存储执行的。这跟我来的时间差不多。 – SonOfPirate 2011-05-02 17:11:00

回答

3

你不应该把你的域模型对象放在客户端。让他们直接在客户端使用会限制您在将来的迭代中演进域的能力,并且在进行DDD时,当您从领域专家那里获得更深入的洞察力时,能够演进您的域是至关重要的。

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

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

我客人的主要想法是为了DRY而不结合概念。像往常一样,乌迪大汉作为此文章:The Fallacy Of ReUse

+0

我会+1您参考Udi Dahan的文章,但我不同意域模型对象不应该在客户端内使用的想法。在某些系统中,让域名可以在本地访问是更有意义的,以减少通过服务器调用服务器的必要性,例如“请验证此单个字段?”。或“根据具体投入计算新的最终价格”。 – jpierson 2011-08-16 20:04:42

+0

与软件开发中的任何东西一样,没有黑色和白色。作为指导,您不应该在客户端使用域模型对象。在实践中,我确信有些情况下,在客户端使用域模型对象是可以接受的 - 但只有在了解采用此路径的全部含义后,才应该这样做。 – 2011-08-17 09:48:38

+0

在客户端没有域模型背后的原因是什么?域的安全/完整性?我可能是错的,但我希望有许多应用程序存在于域仅存在于客户端的地方。除非我们谈论的是一个Web应用程序,否则我认为将这个域独占于N层架构中的服务器或中间层是没有好处的。虽然会对你的想法感兴趣。 – jpierson 2011-08-17 19:02:06

0

考虑使用InternalsVisibleTo属性。

+0

这是我正在考虑的选项之一,但我通常保留授予单元测试访问权限。我有这样的感觉,这可能会使代码有点“臭”。但是,这是我想到的一个考虑因素。 – SonOfPirate 2011-05-02 15:35:22

4

关闭和任何人的缘故是遇到这个职位在未来,我想我会分享什么我结束了,同时给予信贷尤利安帮助转向我在正确的方向。

简而言之,我意识到(在Iulian的帮助下)我确实在客户端应用程序和服务器端服务应用程序之间有两种不同的用例。结果,我咬了嘴唇,为每个人创造了独立的领域模型。

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

不幸的是,这确实带来了一个折衷。我希望在推进技术发展时尽量减少利用RIA服务,这可以让我们将数据注释从服务器传递到客户端。但是,现在我正在使用简单的DTO对象在应用程序和提供API的核心域逻辑的RESTful服务接口之间传递状态信息。

使用我原先所列举的例子,我有以下设置:

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

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

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

因此,对我来说最重要的是将解决方案分成不同的应用程序,这使我能够隔离用例并绘制出每个应用程序所需的适当对象和关系,以满足其目的。现在我有一个解决方案,它也支持多个客户端,因为我已经将客户端与服务器分离开来,使我可以插入新的客户端,而无需更改服务应用程序或现有的客户端应用程序。

HTH