个人建议在服务器端保留映射。你可能已经花了很多时间建立你的设计,别让它白费。
考虑一下什么是Web服务。它不仅仅是对ORM的抽象;它是一个合同,是针对内部和外部客户端的公共API。
一个公共API应该很少变化,如果有的话几乎都会产生破坏性影响,除非是增加新类型和方法。但你的领域模型不会如此严格。你需要随着时间的推移添加新功能或发现原始设计中的缺陷而不断更改它。你需要确保对内部模型的更改不会导致服务合同的级联更改。
为每个消息创建特定的Request
和Response
类实际上是一种常见的做法(我不会侮辱读者的智商,使用“最佳实践”这个词组)。这样做可以更简单地扩展现有服务和方法的能力,而无需进行破坏性更改。
客户端可能并不希望使用与您在服务内部使用的完全相同的模型。如果你只有一个客户端,那么也许这似乎是透明的,但如果你有外部客户端,并且曾经看到过他们对你系统的解释有多么偏离现实,那么你就会理解不让你的完美模型泄露出服务API的范围的价值。
有时,甚至不可能通过API发送你的模型。这种情况有很多原因:
尝试在SOAP/REST上使用某些类型的继承机制可能最多只能算是一种把戏。旧式XML序列化器至少支持 xs:choice
,而 DataContract
则不支持,我不想争辩理由,但可以说你的丰富领域模型中可能有一些多态性,并且很难通过 Web 服务来实现。
懒/延迟加载,在使用ORM时可能会用到。确保它被正确序列化已经够麻烦了 - 例如,使用 Linq to SQL 实体时,WCF甚至都不会触发懒加载器,除非您手动加载,否则它只会将 null
放入该字段 - 但对于返回的数据问题会变得更加严重。在一个领域模型中常见的简单事情,例如在构造函数中初始化的 List<T>
自动属性,在 WCF 中简直无法正常工作,因为它不会调用您的构造函数。相反,您必须添加一个 [OnDeserializing]
初始化方法,而您真的不希望用这种垃圾堆积您的领域模型。
我还刚刚注意到括号中的备注,您正在使用NHibernate。请注意,像 IList<T>
这样的接口完全无法在 Web 服务上进行序列化!如果您使用大多数人使用的NHibernate的POCO类,则这根本不起作用。
此外,您的内部领域模型很可能与客户端的需求不匹配,改变您的领域模型来适应这些需求是没有意义的。以发票为例,它需要显示:
- 关于帐户的信息(帐户号码、名称等)
- 特定于发票的数据(发票号码、日期、到期日等)
- A/R级别信息(先前余额、滞纳费、新余额)
- 发票上的每个产品或服务的信息;
- 等等。
这可能适合领域模型。但是,如果客户想运行显示1200张发票的报告呢?某种对账报告?
这对串行化来说很糟糕。现在你正在发送1200张发票,其中相同的数据一遍又一遍地进行序列化 - 相同的账户,相同的产品,相同的应收账款。在内部,应用程序会跟踪所有链接;它知道发票#35和发票#45属于同一客户,因此共享客户引用;在串行化时,所有这些信息都丢失了,你最终会发送大量冗余数据。
你真正想要的是发送一个定制报告,其中包括:
- 报告中包含的所有账户及其应收账款;
- 报告中包含的所有产品;
- 所有发票,只包括产品和账户ID。
如果你想避免大量冗余,则需要在将传出数据发送到客户端之前对其执行额外的“规范化”。这非常有利于DTO方法;在你的领域模型中包含此结构是没有意义的,因为你的领域模型已经以自己的方式处理了冗余。
希望这些示例和理由足以说服您保留领域与服务契约之间的映射。到目前为止,你已经做得非常正确,你有一个很好的设计,如果为了某些可能会导致重大问题的东西而抵消所有那些努力,那将是一件遗憾的事情。