我个人建议将您的映射保留在服务器端。你可能已经做了很多工作来构建你的设计,直到现在。不要把它扔掉。
考虑一下什么是 Web 服务。它不仅仅是对您的 ORM 的抽象;这是一份合同。它是供内部和外部客户使用的公共 API。
公共 API 应该没有任何改变的理由。除了添加新的类型和方法之外,几乎所有对 API 的更改都是重大更改。但是您的域模型不会那么严格。当您添加新功能或发现原始设计中的缺陷时,您需要不时更改它。您希望能够确保对内部模型的更改不会通过服务合同引起级联更改。
出于类似的原因,为每条消息创建特定的类Request
实际上是一种常见的做法(我不会用“最佳做法”这个词来侮辱读者) ;Response
扩展现有服务和方法的功能变得更加简单,而无需进行重大更改。
客户可能不想要您在服务内部使用的完全相同的模型。如果您是您唯一的客户,那么这似乎是透明的,但是如果您有外部客户并且已经看到他们对您的系统的解释通常有多远,那么您就会理解不允许您的完美模型泄漏的价值超出服务 API 的范围。
有时,甚至无法通过 API 将模型发回。发生这种情况的原因有很多:
对象图中的循环。在 OOP 中非常好;连载的灾难。您最终不得不对图形必须在哪个“方向”进行序列化做出痛苦的永久选择。另一方面,如果您使用 DTO,则可以在任何适合手头任务的方向上进行序列化。
尝试在 SOAP/REST 上使用某些类型的继承机制充其量只能是一个杂项。旧式 XML 序列化器至少支持xs:choice
; DataContract
没有,而且我不会对基本原理争论不休,但只要说您的富域模型中可能有一些多态性就足够了,并且几乎不可能通过 Web 服务来引导它。
延迟/延迟加载,如果您使用 ORM,您可能会使用它。确保它被正确序列化已经很尴尬了——例如,使用 Linq to SQL 实体,WCF 甚至不会触发惰性加载器,它只会放入null
该字段,除非你手动加载它——但问题变得更糟数据返回。像List<T>
在构造函数中初始化的自动属性这样简单的东西 - 在域模型中很常见 - 在 WCF 中根本不起作用,因为它不会调用您的构造函数。相反,您必须添加一个[OnDeserializing]
初始化方法,并且您真的不想用这些垃圾来弄乱您的域模型。
我也刚刚注意到您使用 NHibernate 的括号中的注释。考虑到像这样的接口IList<T>
根本无法通过 Web 服务进行序列化!如果您像我们大多数人一样将 POCO 类与 NHibernate 一起使用,那么这根本行不通。
当您的内部域模型根本不符合客户的需求时,也可能有很多实例,并且更改您的域模型以适应这些需求是没有意义的。作为一个例子,让我们以发票这样简单的事情为例。它需要显示:
- 账户信息(账号、姓名等)
- 发票特定数据(发票编号、日期、到期日等)
- 应收账款级别信息(前余额、滞纳金、新余额)
- 发票上所有内容的产品或服务信息;
- 等等。
这可能很适合域模型。但是,如果客户想要运行显示其中 1200 份发票的报告怎么办?某种和解报告?
这对于序列化来说很糟糕。现在您要发送 1200 张发票,这些发票一遍又一遍地序列化相同的数据 - 相同的帐户、相同的产品、相同的应收帐款。在内部,您的应用程序正在跟踪所有链接;它知道 Invoice #35 和 Invoice #45 是针对同一客户的,因此共享一个Customer
参考;所有这些信息在序列化时都会丢失,您最终会发送大量冗余数据。
您真正想要的是发送一个自定义报告,其中包括:
- 报告中包含的所有账户及其应收账款;
- 报告中包含的所有产品;
- 所有发票,仅带有产品和帐户 ID。
如果要避免大量冗余,则需要在将传出数据发送到客户端之前对其执行额外的“规范化”。这非常有利于 DTO 方法;在您的领域模型中使用这种结构是没有意义的,因为您的领域模型已经以自己的方式处理了冗余。
我希望这些是足够的示例和足够的理由来说服您保持域 <--> 服务合同的映射完好无损。到目前为止,你所做的事情绝对是正确的,你有一个很棒的设计,如果你放弃所有的努力来支持可能会在以后导致严重头痛的事情,那将是一种耻辱。