I'm building a basic CRUD library, that I anticipate use in both local (add reference) and wcf (add service reference) environments.
What are the best return types for the Create, Uupdate, and Delete portions (that have more complex business rules) of a CRUD setup?
I want to be able to minimize back-and-forth on the wire, but I also want to provide my clients with meaningful information about when an operation fails my business logic, but is technically valid (thus it's not an exception).
Take for example the CRUD is for a Person class, which has these fields: FirstName, MiddleName LastName, and Date of Brith. First, Last, and DOB are required, but Middle is not.
How should I convey failures of business logic back to the client? I.E. "You must specify a value for FirstName."
For performance reasons, initial input validation should be performed in your client tier. (i.e.: Don't bother sending bad data down the wire.) In the client tier, an input validation failure would, in fact, be a very expected problem that should not throw exceptions. However, if you put this "early" validation in place in the client tier, a data validation failure encountered at any deeper tier could reasonably be viewed as an unexpected problem, so throwing exceptions for data validation errors in those tiers would not be inappropriate.
1.Is this where I should be throwing exceptions? (it does not feel like an exceptional case, so I think not but I could be wrong).
Personally, I feel that you should return an object with a result as well as any validation errors, and not throw an exception for data validation, whether that's due to missing information (format validation) or business logic validation. However, I do suggest throwing an exception for errors that are not related to the data itself - ie: if the database commit fails with valid data, etc.
My thinking here is that validation failing is not an "exceptional occurance". I personally feel that anything a user can mess up by just not entering enough/correct/etc data is something that is not exceptional - it's standard practice, and should be handled by the API directly.
Things that are not related to what the user is doing (ie: network issues, server issues, etc) are exceptional occurances, and warrant an exception.
2.Should I use void and an "out" parameter? If so, what type should it be?
3.Should I use an object return type and put data in there about what happens?
I personally prefer the third option. "out" parameters are not very meaningful. Also, you're going to want to return more than a single status information from this call - you'll want to return enough information to flag the appropriate properties as invalid, as well as any full operation-wide information.
This is probably going to require a class that contains, at a minimum, a commit status (success/failed format/failed business logic/etc), a list of mappings for properties->errors (ie: IDataErrorInfo style information), and potentially a list of errors that aren't tied to a specific property, but rather deal with business logic of the operation as a whole, or the combination of suggested property values.
4.Some other option that I've missed completely?
The other option, which I like quite a bit, is to have the validation in a separate assembly from the business processing layer. This allows you to reuse the validation logic on the client side.
The nice thing about this is that you can simplify and reduce the network traffic dramatically. The client can pre-validate the information, and only send data across the wire if it's valid.
The server can receive the good data, and revalidate it, and return nothing but a single commit result. I do believe this should have at least three responses - success, failed due to business logic, or failed due to formatting. This gives the security (you don't have to trust the client), and gives the client information about what's not being handled properly, but avoids passing both bad info from client->server, and validation info from server->client, so can drastically reduce traffic.
The validation layer can then (safely) send the info to the CRUD layer to submit.
You might find this blog post by Rob Bagby interesting; he describes how to implement a repository to handle CRUD operations, and, to your point, specifically how to implement validation, returning a collection of "RuleViolation" to the client in case there is a problem.
http://www.robbagby.com/silverlight/patterns-based-silverlight-development-part-ii-repository-and-validation/
[edit] For me, it is a case for throwing an exception: if creating a user necessitates a first name and the first name is not provided, the caller hasn't used the proper arguments, and is not using the method in the intended way. InvalidArgumentException sounds adequate.
You really should check out Rocky Lhotka's implementation of Validation Rules in his CSLA framework.
NOTE: I did not say to use his framework en masse, as it has some coupling issues that does break some SRP efforts in the latest .NET development trends.
But, his framework does make use of "automatic" notification up to the UI layer and integration with validation error messages with support for Web/Winforms controls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With