User recoverable exceptions should not be "exceptions". Exceptions are for exceptional circumstances. Transposing a few letters in a form field is something that you should expect and plan for.
Part of the impetus behind a "Service-Oriented Architecture" is that services are reusable. Sure, it might be a client sending messages to it... or it might be another service, or an orchestration engine, or an event subscriber, or an automated task or batch job. These actors can't possibly be able to reliably recover from a fault, no matter how much detail you put into it. In many cases they may even be using one-way messaging (i.e. MSMQ), in which case you're not even allowed to send a fault back; there's simply no channel for it.
Once a service has made the decision to send back a fault message, assuming that the originator can actually receive it, then all the originator can sensibly do is roll back the transaction it's in - if it was smart enough to enlist in one.
Juval is exactly right. Marshaling fault messages into client exceptions is fine when you've exhausted all other options (i.e. unhandled exception), but there is no point in the service trying to provide all kinds of detail. None. Users will not read or understand the error message, and if you think having a stack trace is a benefit from the user perspective then you don't understand the first thing about usability.
Microsoft actually tells you to put exception detail in faults. But don't. Please don't. It just encourages you to be lazy and fault when you really should be handling the errors. I've been down that road and it is one of never-ending pain and misery. It's especially pernicious in WCF because faulting permanently invalidates the service proxy, and it's actually very difficult to design client apps to recover from this, particularly if you're following other "best practices" and doing dependency injection.
What you should - nay, must be doing is logging all errors on the service side, generally into persistent storage, and sending notifications as bug reports. More sophisticated, service-bus architectures will even have an error queue which holds all of the original messages that caused the errors - but at the very least, you want the errors themselves. You want them - not your users. Don't rely on them to give you the stack traces, because if you do, then you have already failed them.
"User recoverable exceptions" simply do not exist in an SOA. There is no such thing because you can't know in advance who the "user" is going to be. If an exception is recoverable then it should be part of the message - for example, in XML form:
<customerUpdateResponse customerId="123" status="notUpdated">
<validationErrors>
<requiredFieldMissing field="fullName"/>
<maxLengthExceeded field="phone" maxLength="30" actualLength="45"/>
</validationErrors>
</customer>
This is just off the top of my head, but hopefully you get the idea; if an operation can fail for known, documented reasons then that "failure" becomes part of the specification. In this case, the message is sending back an event saying what happened, and the client application can interpret this data appropriately. The important thing is that it is part of the contract, not some unexpected "stop the presses" error.
Now I know that WCF lets you use fault contracts and so on, but honestly, I don't see the point, it's just adding complexity where it's not really needed. SOAP faults are, honestly, a pain in the butt to deal with from any angle.
As mentioned earlier, you also have to carefully plan for the case where you can't send any response. Fledgling "SOAs" with a smattering of web services tend to be predominantly RPC style, but that's actually a poor strategy for designing a robust high-performance architecture. The killer feature of an SOA, in my opinion at least, is publish-subscribe, which allows you to totally decouple the services themselves and only ever share messages. But this comes at a cost: you have to dispense with two-way communication. If a service wants to fault after consuming an event, well, great, but nobody's going to be listening. Which means that proper logging and exception notification is really, really important.
A good overall strategy for the second case is to define a generalized message type for unrecoverable errors (technically you could just use the FaultException
) and install a component in the pipeline which forwards all faults to a fault queue, thus (a) ensuring that you don't lose any, and (b) collecting them all into a central location, which will make your life a whole lot easier when you have 30 different web services on 10 different servers. It's really very easy to set up a global exception handler in WCF - just attach to the Faulted
event of the ServiceHost
. You can also install your own IErrorHandler
to do all of this before the fault ever happens - your choice.
But in summary: Instrument your systems so that you can resolve serious issues proactively and don't fault for recoverable errors. To the end user, downtime is downtime; make the exception details discoverable for developers and support staff but don't leak them to users.
I think versioning is probably the best argument. When you have an existing operation contract like
int GetPersons(int countryId);
that you want to enhance, e.g. by another filter later on
int GetPersons(int countryId, int age);
You would have to write a new operation contract and with a new name since it has to be unique. Or you would keep the name and publish a new v2 of your service with the old v1 still being around for backwards compatibility.
If you wrap the parameter into an object you can always extend it with default/optional parameters and all your existing clients will be unaffected when you reuse the same operation contract.
However I would also urge to name your objects appropriately. Even if it just wraps an int, if you start with IntMessage
or something similar you're not doing yourself a favor extending it. You'd have to name it e.g. PersonFilter
right from the start which means you have to think a little about what this service call should expect as parameter semantically and therefore what it's supposed to do. Maybe (and that's a very vague maybe) that's going to help in developing the right services and maintain a decent sized API.
It allows common data and behavior to be isolated to a base class
That's something to be cautious with. Inheritance and data contracts don't go that well together. It does work, but you'll have to specify all known subtypes of the contract that could go over the wire, otherwise the data contract serializer fails complaining about unknown types.
But what you could do (but still probably shouldn't, I'm still undecided on this) is reuse the same messages among different services. If you put the data contracts in a separate dll you can share that between client and service and you don't need to convert between types when you call different services that expect basically the same message. E.g. you create a PersonFilter and submit that to one service to get a filter list of persons and then to another service and have the same objects on the client. I can't find a good real-world example for that though and the danger is always that an extension to the data-contracts is not general enough for all services that use this contract.
Overall, apart from versioning, I can't really find the killer reason for doing it that way either.
Best Answer
If what you want is fire-and-forget then go with MSMQ directly, or use something like NServiceBus or MassTransit. The problem with WCF is that in attempting to abstract transport details, it ends up hiding aspects that should be explicit. The cost of abstraction is too high in my opinion.
You can use WebAPI in conjunction with MSMQ+NServiceBus. They solve very different problems. In a stereotypical architecture, you'd have a web service implemented with WebAPI which sends messages, in a fire-and-forget manner, to a messaging technology. It can also serve queries which can return processing status and results, for instance.
Whether you use WebAPI or WCF depends on your requirements. If there is a need to use TCP then go with WCF. If you can use HTTP, I'd go with WebAPI. It makes HTTP explicit and easier to work with. After all, HTTP is already a rich application protocol, why hide it?