将技术与领域对象解耦
我公司有一个应用程序,允许用户执行测量循环血量的诊断医学测试。该测试涉及使用伽马计数器来测量/计算多个血液样本中的辐射。这些样品被放置在机动转盘(即样品更换器)中,该转盘将样品移动到伽玛计数器上进行计数。
我们认为我们的领域名词如下:
- 测试(即,血量、质量控制)
- 患者
- 样本(即,要计数的东西)
- 谱图
- 测试执行上下文(即,样本的规范及其对应于某种类型的顺序)测试)
我们相信我们的领域动词是:
- 将轮播移动到给定位置
- 获取频谱(即计算样本)
- 运行测试
当我们理解领域驱动设计时,业务逻辑应该进入领域 。我们的业务逻辑主要驻留在我们所谓的测试执行控制器中。该控制器使用测试执行上下文来确定如何将样本移动到位并让伽玛计数器测量它们。
给我们带来一些困惑的具体技术是 Prism 和 WCF。
(1) Prism 依赖事件聚合器在系统中传递非 CLR 事件。测试执行控制器使用它来让系统的其他部分知道发生了什么(例如,正在对样本2A进行计数,当前测试还剩34分钟)。从技术上讲,事件聚合器是 Prism 的一部分技术,域对象/服务不应依赖于技术。
问题:有没有办法重组事物,使我们的域服务不依赖于技术?
(2) 我们有两个 WCF 服务,允许我们与样品更换器和伽玛计数器进行通信。每个服务都有一个契约(即用 WCF 特定属性修饰的接口)。至少通过合同,我们将关注点分开,以便我们的主要应用程序取决于行为,而不是特定的样本更换器或伽玛计数器。然而,WCF 是一种技术,应用程序代码需要知道我们正在谈论的这个服务是一个 WCF 服务(这是通过创建代理类来完成的)。为了满足 DDD 约束,我们最终得到了几个看似多余的类似命名的类/接口。下面是一个示例
- IGammaCounterService – WCF 合约,定义了与伽玛计数器通信的方法。此接口由 (1) 实际实现所在的 WCF 端以及 (2) 与此服务通信的应用程序代码引用。
- IGammaCounter – 定义伽马计数器行为的属性/方法集。 (这是我们域的一部分。)
- GammaCounterProxy – 实现 WCF 服务协定的类。这是我们的应用程序用来与 WCF 服务通信的。
- GammaCounter – 业务逻辑使用的类。这是一个 GammaCounterProxy(通过继承)并且还实现了 IGammaCounter。 (注意:我们使用控制容器反转 - 特别是 Unity - 在我们的应用程序中注册此实例。)
问题:我们在域和 WCF 端都有接口,它们基本上具有相同的方法名称。有没有更好/更干净的方法来做到这一点?
My company has an application that allows users to perform a diagnostic medical test that measures circulating blood volume. This test involves using a gamma counter to measure/count the radiation in multiple blood samples. These samples are placed in a motorized carousel (i.e., sample changer) that moves the samples over the gamma counter to be counted.
Here’s what we believe our domain nouns are:
- Test (i.e., blood volume, quality control)
- Patient
- Sample (i.e., something to be counted)
- Spectrum
- Test execution context (i.e., specification of the samples and their order that correspond to a certain type of test)
We believe our domain verbs are:
- Moving the carousel to a given position
- Obtaining a spectrum (i.e., counting a sample)
- Running tests
As we understand domain driven design, the business logic should go in the domain. Our business logic resides mainly with what we’re calling a test execution controller. This controller uses a test execution context to determine how to move the samples into position and to have the gamma counter measure them.
The specific technologies that are giving us some confusion are Prism and WCF.
(1) Prism relies on an event aggregator to pass non-CLR events around the system. The test execution controller uses this to let other parts of the system know what’s going on (e.g., Sample 2A is being counted, there are 34 minutes remaining for the current test). Technically, the event aggregator is a technology that’s part of Prism, and domain objects/services are not supposed to rely on technology.
Question: Is there a way to restructure things so that our domain service isn’t technology-dependent?
(2) We have two WCF services that allow us to communicate with the sample changer and gamma counter. Each service has a contract (i.e., an interface that’s decorated with WCF-specific attributes). At least with the contracts, we separate the concerns such that our main application depends on behavior, rather than a specific sample changer or gamma counter. However, WCF is a technology and the application code needs to know that this service we’re talking to is a WCF service (which is done by creating a proxy class). To satisfy DDD constraints, we end up having several similarly named classes/interfaces that seem redundant. Here’s an example
- IGammaCounterService – WCF contract that defines methods to communicate with a gamma counter. This interface is referenced by (1) the WCF side of things where the actual implementation lives, and (2) application code that talks to this service.
- IGammaCounter – set of properties/methods that defines the behavior for a gamma counter. (This is part of our domain.)
- GammaCounterProxy – class that implements the WCF service contract. This is what our application uses to communicate with the WCF service.
- GammaCounter – class that is used by the business logic. This is a GammaCounterProxy (via inheritance) and also implements IGammaCounter. (Note: We use an inversion of control container – specifically Unity – to register this instance within our application.)
Question: We have interfaces in the domain and on the WCF side that basically have the same method names. Is there a better/cleaner way to do this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
WCF 可以让您通过端点公开您的服务来很好地分离您的关注点。这些端点可以支持:
)例如,在我的 Magic8Ball WCF 示例 中,我同时通过多个端点公开 Magic8Ball 服务:二进制 XML/TCP 用于从 WCF 客户端进行(非常)快速的访问,XML/HTTP 用于非 WCF 客户端,JSON/HTTP 用于 REST 客户端。
如果您愿意,这些端点配置可以完全在您的 app.config 中表达,这样您就不必修改源代码和源代码。如果您的环境需要更改,请重建/重新部署您的服务。
因此,每个“控制器”都可以作为服务公开,每个名词都可以作为可序列化的数据实体公开。如果您从名词中消除除最基本的业务逻辑之外的所有内容,那么任何平台上的任何调用应用程序都应该能够形成格式正确的消息,其中包含以正确格式编码的数据,并通过支持的传输将其传递到您的服务。
通过这种方式,您可以实现大量的技术抽象,这样,如果您将来选择使用不同的技术/平台构建系统的某些部分,只要遵守有线协议,您的环境中的任何内容都不会太在意。
WCF can allow you to separate your concerns quite nicely by exposing your services via endpoints. These endpoints can support:
For example, in my Magic8Ball WCF sample, I expose the Magic8Ball Service via several endpoints simultaneously: binary-XML/TCP for (very) fast access from WCF clients, XML/HTTP for non-WCF clients, JSON/HTTP for REST clients.
These endpoint configurations can be expressed entirely in your app.config should you wish so that you don't have to modify your source code & rebuild/redeploy your services should your environment needs change.
So, each of your "controllers" could be exposed as services and each of your nouns as serializable data entities. If you eliminate all but the most fundamental of your business logic from your nouns, then any calling application on any platform should be able to form a correctly formatted message containing data encoded in the correct format and deliver it via the supported transport to your service.
In this way, you can achieve a great deal of technology abstraction such that should you choose in the future to build some part of your system using a different technology/platform nothing in your environment should care much so long as the wire protocols are adhered to.
解决方案中要考虑的另一个结构轴是应用程序层集。例如,在本例中,您似乎有一个表示层(使用 Prism/WPF 实现)、一个服务层(由 WCF 实现)和一个业务/域层(使用 DDD 实现)。
如果测试执行控制器是MVC意义上的控制器,那么它不应该包含业务逻辑。相反,它的工作是协调 UI 和业务层之间的交互。在这种特殊情况下,控制器应通过 WCF 实现的服务层将命令转发到业务层。然而,控制器本身是表示层的一部分,因此它依赖于特定于技术的组件(例如事件聚合器)这一事实并不是问题,因为您已经选择了 WCF/Prim 作为表示层的技术 -没有必要将其抽象化,尤其是过早地抽象化。
通常,当 DDD 业务层通过 WCF 作为服务公开时,就会产生这种类型的双类层次结构,因为您创建了 DTO (数据契约)映射到域实体和值。 DTO 没有行为,它们的核心作用是表示进出服务的数据。很多时候,DTO 看起来与相应的域对象非常相似,并且存在这种重复是可以接受的。如果这仍然是一个问题,您可以查看映射库,例如 AutoMapper。
就组织解决方案而言,您有几种选择。一是对表示层完全隐藏领域层,只允许通过服务层访问。因此,表示层项目 (WPF/Prism) 将引用一个包含服务协定和关联数据协定 (DTO) 的精简项目。这可以是一个定义服务层“架构”的小项目。该项目还将被实现服务合同的 WCF 项目引用。这种方法的好处是域完全由服务层封装。您可以通过重新部署服务来更改服务契约的实现 - 也无需重新部署表示层。缺点是它可能有点过分,或者可能不适合将域公开为服务。
另外,我对你的命名约定和结构有点困惑。例如,GammaCounterProxy 不是代理,该代理是在您获取对服务协定 IGammaCounterService 的引用时由 WCF 生成的。服务实现类本身只是一个实现,而不是代理。另外,GammaCounter 继承自 GammaCounterProxy 似乎是错误的。对于 GammaCounter 来说,直接实现 IGammaCounter 并让 GammaCounterProxy 使用该对象(重命名为 GammaCounterImpl 之类的名称)会更有意义。 GammaCounterImpl 的工作是处理一些命令(由 DTO 参数表示)。它将通过加载所有适当的域对象(例如 GammaCounter),调用它们的方法,然后可能返回一个结果(作为 DTO)来实现这一点,然后由表示层处理该结果。
An additional axis of structure to consider within a solution is the set of application layers. For example, it appears that in this case you have a presentation layer (implemented using Prism/WPF), a service layer (implemented by WCF) and a business/domain layer (implemented using DDD).
If the test execution controller is a controller in the MVC sense, then it should not contain business logic. Instead, its job is to coordinate interactions between the UI and the business layer. In this particular case, the controller should forward commands to the business layer through a WCF implemented service layer. However, the controller itself is part of the presentation layer and so the fact that it depends on a technology specific component such as the event aggregator isn't a problem since you've already chosen WCF/Prim as the technology for the presentation layer - there is no point to abstract it, especially prematurely.
Normally this type of dual-class hierarchy results when a DDD business layer is exposed as a service via WCF because you create DTOs (data contracts) which map to domain entities and values. The DTOs don't have behavior, their central role is to represent data coming in and out of a service. Many times a DTO will look very similar to the corresponding domain object and it is acceptable to have this duplication. If this is still a concern you can look into a mapping library such as AutoMapper.
As far as organizing the solutions you have a few options. One is to hide the domain layer completely from the presentation layer and only allow access to it through the service layer. So the presentation layer project (WPF/Prism) would reference a thin project which contains the service contract and the associated data contracts (DTOs). This can be a small project which defines the "schema" of your service layer. This project would also be referenced by the WCF project which implements the service contract. The benefit of this approach is that the domain is entirely encapsulated by the service layer. You can change the implementation of the service contract by re-deploying your service - no need to redeploy the presentation layer as well. The drawback is that it might be overkill or it might not be appropriate for the domain to be exposed as a service.
Also, I'm a little confused by your naming conventions and structure. For example, GammaCounterProxy isn't a proxy, the proxy is generated by WCF when you get a reference to the service contract IGammaCounterService. The service implementing class itself is just an implementation, not a proxy. Also, it seems wrong for GammaCounter to inherit from GammaCounterProxy. It would make more sense for GammaCounter to implement IGammaCounter directly, and have that object be used by GammaCounterProxy (renamed to something like GammaCounterImpl). The job of GammaCounterImpl is to process some command (represented by the DTO parameters). It would do that by loading all appropriate domain objects, such as GammaCounter, invoking methods on them, and then possibly returning a result (as a DTO) which would then be processed by the presentation layer.