We implement the majority of our business rules in the database, using stored procs.
I can never decide how best to pass data constraint violation errors from the database back to the user interface. The constraints I'm talking about are tied more to business rules than data integrity.
For example, a db error such as "Cannot insert duplicate key row" is the same as the business rule "you can't have more than one Foo with the same name". But we've "implemented" it at the most common sense location: as a unique constraint that throws an exception when the rule is violated.
Other rules such as "You're only allowed 100 Foos per day" do not cause errors per-say, since they're gracefully handled by custom code such as return empty dataset
that the application code checks for and passes back to the ui layer.
And therein lies the rub. Our ui code looks like this (this is AJAX.NET webservices code, but any ajax framework will do):
WebService.AddFoo("foo", onComplete, onError); // ajax call to web service
function onComplete(newFooId) {
if(!newFooId) {
alert('You reached your max number of Foos for the day')
return
}
// update ui as normal here
}
function onError(e) {
if(e.get_message().indexOf('duplicate key')) {
alert('A Foo with that name already exists');
return;
}
// REAL error handling code here
}
(As a side note: I notice this is what stackoverflow does when you submit comments too quickly: the server generates a HTTP 500
response and the ui catches it.)
So you see, we are handling business rule violations in two places here, one of which (ie the unique constaint error) is being handled as a special case to the code that is supposed to handle real errors (not business rule violations), since .NET propagates Exceptions all the way up to the onError()
handler.
This feels wrong. My options I think are:
"select name from Foo where name = @Name"
) and return whatever it is the app server expects as the "business rule violated" flag,insert into Foo
, catching any exceptions and convert it to whatever it is the app server expects as the "business rule violated" flaginsert into Foo
(like 3) and let that Exception propagate to the ui, plus have the app server raise business rule violations as real Exceptions
(as opposed to 1). This way ALL errors are handled in the ui layer's onError()
(or similar) code.What I like about 2) and 3) is that the business rule violations are "thrown" where they are implemented: in the stored proc. What I don't like about 1) and 3) is I think they involve stupid checks like "if error.IndexOf('duplicate key')"
, just like what is in the ui layer currently.
Edit: I like 4), but most people say to use Exception
s only in exceptional circumstances.
So, how do you people handle propagating business rule violations up to the ui elegantly?
Solution that is possible to correct such violation is if any insertion violates any of the constraints, then the default action is to reject such operation. Deletion operation: On deleting the tuples in the relation, it may cause only violation of Referential integrity constraints.
Integrity constraint violations occur when an insert, update, or delete statement violates a primary key, foreign key, check, or unique constraint or a unique index.
A constraint violation is simply a grammatical error or a value that does not adhere to the LDAP schema. For example , you may be creating a user and providing characters that are not allowed for an attribute. Example: The telephone number attribute has schema that allows only numbers.
To handle unique constraint violations: Catch uniqueness exceptions thrown by the database at the lowest level possible — in the UnitOfWork class. Convert them into Result. Use the UnitOfWork in the controller to explicitly commit pending changes and see if there are any uniqueness constraint violations.
We don't perform our business logic in the database but we do have all of our validation server-side, with low-level DB CRUD operations separated from higher level business logic and controller code.
What we try to do internally is pass around a validation object with functions like Validation.addError(message,[fieldname])
. The various application layers append their validation results on this object and then we call Validation.toJson()
to produce a result that looks like this:
{
success:false,
general_message:"You have reached your max number of Foos for the day",
errors:{
last_name:"This field is required",
mrn:"Either SSN or MRN must be entered",
zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible"
}
}
This can easily be processed client side to display messages related to individual fields as well as general messages.
Regarding constraint violations we use #2, i.e. we check for potential violations before insert/update and append the error to the validation object.
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