.NET 中的分布式 DDD:与客户端共享域对象
我正在开发一个 3 层应用程序(不是 3 层!),其中一个客户端应用程序运行在一层(物理集群)上,该应用程序与运行在另一层上的服务应用程序和运行在另一层上的数据库服务器进行交互。该应用程序有很多业务规则、流程逻辑等,我认为这些规则、流程逻辑等都应该在应用程序层和服务层上可用,以改善用户体验、减少对服务的调用以及消除冗余编码。
让我们使用这个例子:在我的域层中,我有一个 Document 对象。该对象包含一个AllowPublish 属性,该属性检查对象的内部状态,如果该状态允许发布文档,则返回true/false。该对象还有一个 Publish 方法,该方法通过将 IsPublished 标志设置为 true 并引发 Published 域事件来修改对象的内部状态,以反映对象正在发布的事实。
我有一个单独的 AuthorizationService 来确定是否允许当前用户发布,以及一个将对象保存到数据库的 DocumentRepository 。
在我的服务应用程序中,我的 DocumentService 有一个 PublishDocument 方法,该方法接受文档 id,使用该 id 从存储库检索文档,检查 AllowPublish 属性,如果为 true,则调用 Publish,然后使用存储库保留更新的对象。
我对客户端的行为略有不同。在这种情况下,我使用AllowPublish 属性来启用/禁用命令按钮。启用并单击后,我调用一个服务代理,该代理公开接受文档 ID 的 PublishDocument 方法。代理将调用传递给服务应用程序的同名 DocumentService 方法。
为了消除重复代码、共享业务逻辑、验证规则等,我将域对象放置在由客户端应用程序和服务应用程序共享的单独程序集中。这意味着客户端应用程序现在可以访问我的 Document 类的 Publish 方法,即使它只是相关的并且只能由我的服务应用程序使用。这让我重新考虑我正在采取的整个方法。
虽然我了解使用 DTO 在客户端和服务器之间传递状态,但我正在使用 .NET 3.5,并且据我所知,共享程序集是与客户端应用程序共享业务和验证规则的唯一方法。我有一些想法,我还可以走哪些其他方向,但希望在踏上新道路之前得到一些建议。
另一方面,我当前对客户端的实现采用了我认为是一种迂回的授权方法,这可能只是表明不同的模型会更好。就像我的服务器端服务应用程序中有一个 AuthorizationService,DocumentService 使用它来执行授权一样,我的客户端代码也有一个类似的代理。这意味着我需要在客户端代码中使用另一层间接层来支持授权,可能是控制器或视图模型。如果用例是有效的,那很好。
编辑
我可能需要澄清一下,编辑文档时,AllowPublish 属性是动态的。第一次检索时,它可能是假的,但随着业务规则的满足,它会变成真。在客户端应用程序中运行业务规则使我们能够提供更丰富的用户体验。
I am developing a 3-tier application (not 3-layer!) with a client application running on one tier (physical cluster) that interacts with a service application running on another tier and the database server on yet another tier. The application has a lot of business rules, process logic, etc. that I believe should be available on both the app and service tiers to improve the user's experience, reduce calls to the service as well as eliminate redundant coding.
Let's use this example: In my domain layer, I have a Document object. This object contains an AllowPublish property which examines the internal state of the object and returns true/false if the state allows the document to be published. The object also has a Publish method which modifies the internal state of the object to reflect the fact that it is being published by setting the IsPublished flag to true and raising the Published domain event.
I have a separate AuthorizationService which determines if the current user is allowed to publish as well as a DocumentRepository which persists the object to the database.
In my service application, my DocumentService has a PublishDocument method that accepts the document id, retrieves the document from the repository using the id, checks the AllowPublish property and, if true, calls Publish then persists the updated object using the repository.
I have slightly different behavior on the client. In that case, I use the AllowPublish property to enable/disable command buttons. When enabled and clicked, I call a service agent which exposes a PublishDocument method accepting the document id. The agent passes the call onto the service application's DocumentService method of the same name.
To eliminate duplicate code, share business logic, validation rules et al, I have placed the domain objects in a separate assembly is shared by both the client application and service application. This means that the client application now has access to the Publish method of my Document class even though it is only relevant and should only ever be used by my service application. This is making me reconsider the entire approach I am taking.
While I understand the use of DTO's to pass state between the client and server, I am using .NET 3.5 and as far as I am aware, sharing the assembly is the only way to share the business and validation rules with the client application. I have some ideas what other directions I can go but was hoping to get some suggestions before embarking down a new path.
On another note, my current implementation for the client takes what I consider to be a round-about approach to authorization that may just be an indicator that a different model would be better. Much like I have an AuthorizationService in my server-side service application that the DocumentService uses to perform authorization, I have a similar agent that my client code uses. This means that I need another layer of indirection in my client code to support authorization, perhaps a Controller or ViewModel. Which is fine if the use case is a valid one.
EDIT
I may need to clarify that the AllowPublish property is dynamic when the Document is being edited. When first retrieved, it may be false but will become true as the business rules are satisfied. Having the business rules running in the client app allows us to provide a richer user experience.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
为了结束以及将来看到这篇文章的任何人,我想我会分享我最终得到的结果,同时感谢 Iulian 帮助引导我走向正确的方向。
简而言之,我意识到(在 Iulian 的帮助下)我确实在客户端应用程序和服务器端服务应用程序之间有两个不同的用例。结果,我咬紧牙关,为每个模型创建了单独的域模型。
我的部分思考过程是逻辑上分离应用程序本身。虽然客户端应用程序没有服务应用程序就无法运行,但我调整了思路,将这种关系更多地视为数据访问层,而不是应用程序和域层。同时,我将观点转向服务器端,将服务接口视为该应用程序的表示层。因此,拥有不同的域对象/层是非常有意义的。
不幸的是,这确实需要权衡。我希望在我们推进技术发展以利用 RIA 服务时尽量减少这种情况,也许,这将允许我们将数据注释从服务器传递到客户端。但目前,我使用简单的 DTO 对象在应用程序和 RESTful 服务接口之间传递状态信息,该接口将 API 提供给核心域逻辑。
使用我最初引用的示例,我进行了以下设置:
当用户单击 UI 中的“发布”按钮时,客户端应用程序调用我的服务代理类上的 Publish 方法。服务代理处理与服务器端服务的通信。在这种情况下,DocumentService 公开一个 Publish 方法,该方法接受要发布的 Document ID(以及用户信息等)。
DocumentService 从 DocumentRepository 检索 Document 域对象,并调用该对象上的 Publish 方法来更新文档的内部状态。然后,该服务调用 DocumentRepository 上的 Update 方法,传入更新的 Document 对象,并将更改保存到数据库中。
权衡是我需要有逻辑/规则来确定文档是否以及何时可以在客户端和服务器上发布(因为我们不能假设请求始终有效)。同样,将解决方案视为两个具有自己的用例集的独立应用程序有助于使这一点更加合理(在我看来)。正如您所看到的,我不需要在文档的客户端版本中使用 Publish 方法,但我确实需要更改跟踪以获得丰富的用户体验。我不需要在服务器上进行相同类型的更改跟踪,因为对象更改时不会刷新 UI。在后一种情况下,变更跟踪由 ORM 实现,以使持久性更加优化。
因此,对我来说最重要的是,将解决方案分离到不同的应用程序中,使我能够隔离用例并绘制每个应用程序满足其目的所需的正确对象和关系。现在我有了一个还支持多个客户端的解决方案,因为我已经将客户端与服务器解耦,允许我插入新客户端,而无需更改服务应用程序或现有客户端应用程序。
华泰
For the sake of closure and anyone that comes across this post in the future, I thought I'd share what I ended up with while giving credit to Iulian for helping steer me in the right direction.
Simply put, I realized (with Iulian's help) that I really did have two different use-cases between the client app and the server-side service app. As a result, I bit my lip and created separate domain models for each.
Part of my thought process was to separate the applications themselves logically. While the client application can't run without the service application, I adjusted my train of thought to see this relationship more as the data access layer than the application and domain layers. At the same time, I shifted my view on the server-side to see the service interface as the presentation layer for that application. As a result, having different domain objects/layers made perfect sense.
Unfortunately this does come with a trade-off. One that I hope to minimize as we advance the technology forward to make use of RIA services, perhaps, which will allow us to pass Data Annotations from server to client. For now, however, I am using simple DTO objects to pass state information between the applications and a RESTful service interface that provides the API into core domain logic.
Using the example I originally cited, I have the following setup:
When the user clicks the "Publish" button in the UI, the client application calls the Publish method on my service agent class. The service agent handles communication with the server-side service. In this case, the DocumentService exposes a Publish method which accepts the ID of the Document to publish (as well as user info, etc.)
The DocumentService retrieves the Document domain object from the DocumentRepository and calls the Publish method on the object which updates the internal state of the Document. The service then calls the Update method on the DocumentRepository, passing in the updated Document object, and the changes are persisted to the database.
The trade-off is that I need to have the logic/rules that determine if and when a Document may be published on both the client and the server (because we can't assume the request is always valid). Again, treating the solution as two separate applications with their own set of use cases helped make this more reasonable (in my mind). As you can see, I don't need a Publish method in the client version of Document but I do need change tracking for a rich user experience. I don't need the same type of change tracking on the server because there is no UI refreshing as the object is changed. In the latter case, change tracking is implemented by the ORM to make persistance more optimal.
So, bottom-line for me is that separating the solution into different applications allowed me to isolate the use cases and draw out the proper objects and relationships required for each application to satisfy its purpose. And now I have a solution that also supports multiple clients because I've decoupled the client from the server in a way that allows me to plug-in new clients without requiring changes to the service application or the existing client applications.
HTH
您不应该将域模型对象放在客户端中。让它们直接在客户端中使用将限制您在未来迭代中发展领域的能力,并且在进行 DDD 时,当您从领域专家那里获得更深入的见解时发展您的领域的能力至关重要。
我不知道这在您的情况下是否可行,但也许您可以将业务规则分解为一些策略对象,这些对象只具有非常特定的行为,可以在域模型和客户端中使用。如果您的目标是避免逻辑重复并且您需要的行为完全相同(但情况可能并非如此),那么这可能没问题。在您的客户端中,您可能需要一些额外的验证步骤,这些步骤可能与域模型中所需的步骤不同。
当您可以在 ViewModel 中进行客户端验证(如果可能的话)基于一些共享规则时,最好的解决方案可能是使用 MVC 或 MVVM 模式。
我认为主要的想法是不要为了干燥而耦合概念。像往常一样,Udi Dahan 对此发表了一篇文章:重用的谬误
You should not put your domain model objects in the client. Having them used in the client directly will limit your ability to evolve the domain in future iterations and when doing DDD the ability to evolve your domain when you get deeper insights from the domain experts is vital.
I don't know if this is possible in your case, but maybe you can factor out the business rules as some strategy objects, which will only have very specific behavior that can be used both in the domain model and in the client. This might be ok if your goal is to avoid logic duplication AND if the behavior you need is exactly the same - which might not be the case. In your client you might need some additional steps for the validation which might be different from the steps you need in the Domain Model.
Probably the best solution is to use a MVC or MVVM pattern when you can have the client validation in a ViewModel, if possible based on some shared rules.
I guest the main idea is don't couple concepts for the sake of DRY. As usual Udi Dahan as an article on this: The Fallacy Of ReUse
考虑使用 InternalsVisibleTo 属性。
Consider using the InternalsVisibleTo attribute.