views:

438

answers:

6

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."

  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).
  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?
  4. Some other option that I've missed completely?
+1  A: 

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.

Mathias
So then would your method look like this? `CreateUser(String firts, String middle, String Last, DateTime dob)` becuase I find myself more inclined to use `CreateUser(User user)` is that a bad design idea? In the non-WCF version I may also add overloads, but I would use the signature that accepts an object in WCF.
Nate Bross
When you throw exceptions, how do you know what area to point the user toward on the user interface? I like when you forget to enter a field, and hit submit, the application not only gives you a message, but also focuses your cursor at that field.
Nate Bross
The method name CreateUser implies that you would be creating a User object; if you already have a User object to pass into the method, why would you be creating one?
Jeremy Seghi
Maybe it should be named CreateUserInDatabase, because I'm thinking that as a client, I have the DataContract for User, I've bound it's properties to my UI Fields, when the user hits the button, I submit that object to the service.
Nate Bross
+1  A: 

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.

Brett Veenstra
+1  A: 

Edit: I should note, I would normally have the validation stuff abstracted into a business layer, that would handle the validation and call the CRUD methods after the validation was successful.

One way to go about this would be to pass back a "response" class that holds all of the information a consumer of your library would need to evaluate what happened and what to do next. A very basic example of a class you could use would be something like this:

public class Response<T> where T:BusinessObject
{
 public Response(T oldOriginalValue, T newValue)
 {
 }

 /// <summary>
 /// List of Validation Messages
 /// </summary>
 public List<ValidationMessage> ValidationMessages { get; set; }

 /// <summary>
 /// Object passed into the CRUD method
 /// </summary>
 public T OldValue { get; private set; }

 /// <summary>
 /// Object passed back from the CRUD method, with values of identity fields, etc populated
 /// </summary>
 public T NewValue { get; private set; }

 /// <summary>
 /// Was the operation successful
 /// </summary>
 public bool Success { get; set; }
}

public class ValidationMessage
{
 /// <summary>
 /// Property causing validation message (i.e. FirstName)
 /// </summary>
 string Property { get; set; }

 /// <summary>
 /// Validation Message text
 /// </summary>
 string Message { get; set; }
}
Greg Andora
I guess I should mention that my real "CRUD" is using LinqToSQL... I'm exposing the business logic in the same form as CRUD. Is that bad?
Nate Bross
For example, what I'm calling CRUD above, would really does the validation of the input and then call LinqToSQL to update the database or fail, and the fail part is what I'm not sure how to do, but I like your idea.
Nate Bross
No, that is fine. Your CRUD methods are really your business layer and the LinqToSql is your data layer and what you are calling your CRUD methods would return the response object to indicate success or fail as well as provide a list of messages to describe the reasons for a failure.Any errors that come from your LinqToSql caught from your LinqToSql could also go into your ValidationMessage collection (which might be better named Messages so it can hold validation and error messages from LinqToSql without confusing anybody)
Greg Andora
+1  A: 

The debate between returning a result structure with details of the failure versus throwing an exception can be generalized to "Can I successfully perform the requested action?" You are designing a library and how the library communicates failure modes is part of that.

If the library is asked to create a User object but can't because the user name failed validation, you could pass back an empty User along with the validation failure messages and hope that the client code tests the return value. My experience (DCOM pre ATL flashbacks) with having to test return values for error codes is that it's easy to get complacent (or lazy) and skip it.

From what you've described, using exceptions isn't out of the question. Define a parent exception for all exception types your library can throw. That way clients don't have to include a large list of sub-exceptions unless they really want to. An example of this is SqlException.

Kelly French
+3  A: 

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.

Nicole Calinoiu
For security, data validation MUST be done at the server, since the client could be compromised. Am I wrong in always treating the client as hostile?
Nate Bross
You're absolutely correct to treat the client as hostile. Client-side validation does not replace service-side validation -- it complements it.All I'm suggesting is that if you apply validation both at the client and at the service interface, most invalid data should never reach the service, so it would be acceptable for the service to treat data validation problems as exceptional.
Nicole Calinoiu
I've doubled back to this after some reflection, and somehow cannot accept an answer, possibly because this is an old question. I've come to the same conclusion.
Nate Bross
+1  A: 

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.

Reed Copsey
Thank you for taking the time to write such a detailed response. What, in your opinion, is the best option for most cases? It seems that #3 is very popular from the other responses, would you agree it is the best "catch all" solution? Baring specail or odd edge cases?
Nate Bross
If you're worried about traffic, my option 4 is a variation on 3 that provides a "better" alternative, IMO. It also makes client-side validation a breeze. Otherwise, option 3 is my favorite.
Reed Copsey