I would personally recommend keeping your mapping on the server side. You've probably done a lot of work building up your design to the point it's at now; don't throw that away.
Consider what a web service is. It is not merely an abstraction over your ORM; it is a contract. It is a public API for your clients, both internal and external.
A public API should have little if any reason to change. Almost any change to an API, aside from adding new types and methods, is a breaking change. But your domain model is not going to be so strict. You will need to change it from time to time as you add new features or discover flaws in the original design. You want to be able to ensure that changes to your internal model do not cause cascading changes through the service's contract.
It's actually a common practice (I won't insult readers with the phrase "best practice") to create specific Request
and Response
classes for each message for a similar reason; it becomes much simpler to extend the capability of existing services and methods without them being breaking changes.
Clients probably don't want the exact same model that you use internally in the service. If you are your only client, then maybe this seems transparent, but if you have external clients and have seen just how far off their interpretation of your system can often be, then you'll understand the value of not allowing your perfect model to leak out the confines of the service API.
And sometimes, it's not even possible to send your model back through the API. There are many reasons why this can occur:
Cycles in the object graph. Perfectly fine in OOP; disastrous in serialization. You end up having to make painful permanent choices about which "direction" the graph must be serialized in. On the other hand, if you use a DTO, you can serialize in whichever direction you want, whatever suits the task at hand.
Attempting to use certain types of inheritance mechanisms over SOAP/REST can be a kludge at best. The old-style XML serializer at least supports xs:choice
; DataContract
doesn't, and I won't quibble over rationale, but suffice it to say that you probably have some polymorphism in your rich domain model and it's damn near impossible to channel that through the web service.
Lazy/deferred loading, which you probably make use of if you use an ORM. It's awkward enough making sure it gets serialized properly - for example, using Linq to SQL entities, WCF doesn't even trigger the lazy loader, it'll just put null
into that field unless you load it manually - but the problem gets even worse for data coming back in. Something as simple as a List<T>
auto-property that's initialized in the constructor - common enough in a domain model - simply does not work in WCF, because it doesn't invoke your constructor. Instead you have to add an [OnDeserializing]
initializer method, and you really don't want to clutter up your domain model with this garbage.
I also just noticed the parenthetical remark that you use NHibernate. Consider that interfaces like IList<T>
cannot be serialized at all over a web service! If you use POCO classes with NHibernate, as most of us do, then this simply won't work, period.
There will also likely be many instances when your internal domain model simply does not match the needs of the client, and it makes no sense to change your domain model to accommodate those needs. As an example of this, let's take something as simple as an invoice. It needs to show:
- Information about the account (account number, name, etc.)
- Invoice-specific data (invoice number, date, due date, etc.)
- A/R-level information (previous balance, late charges, new balance)
- Product or service information for everything on the invoice;
- Etc.
This probably fits fine within a domain model. But what if the client wants to run a report that shows 1200 of these invoices? Some sort of reconciliation report?
This sucks for serialization. Now you're sending 1200 invoices with the same data being serialized over and over again - same accounts, same products, same A/R. Internally, your application is keeping track of all the links; it knows the Invoice #35 and Invoice #45 are for the same customer and thus share a Customer
reference; all of this information is lost upon serialization and you end up sending a ridiculous amount of redundant data.
What you really want is to send a custom report that includes:
- All accounts included in the report, and their A/R;
- All products included in the report;
- All of the invoices, with Product and Account IDs only.
You need to perform additional "normalization" on your outgoing data before you send it to the client if you want to avoid the massive redundancy. This heavily favours the DTO approach; it does not make sense to have this structure in your domain model because your domain model already takes care of redundancies, in its own way.
I hope those are enough examples and enough rationale to convince you to keep your mappings from Domain <--> Service Contract intact. You've done absolutely the right thing so far, you have a great design, and it would be a shame to negate all that effort in favour of something that could lead to major headaches later on.