Business Rule Violation - violates what ?
Typically when a business rule is violated, the domain has a set of alternative rules, which kicks in to handle the situation. It's not that we have a fatal exception that needs to fall flat on the user's face. When I have an
account.debit(amount)
failing the balance check, there is an alternate path that the domain prescribes and which needs to be modeled as part of the implementation. So, instead of violation, I prefer to use the term alternate business scenario, since it's always choosing between one of the many pathways through which the model flows in course of the domain lifecycle. One of them is the most commonly executed path, while the others may be less frequently traversed, but very much a part of the domain logic.In the last post, I had talked about Java's checked exceptions as a possible means to handle what I call alternate business scenarios. Many people has, since then, suggested the option of using the Specification pattern of domain driven design to handle alternate execution paths in the domain model. In this post, let us see how much we can improve using the Specification pattern both with respect to encapsulating the business constraints and also handling alternate sequence of execution on violation of those constraints.
Using the Specification pattern to handle alternate business scenarios
I take the example from my previous post, modified slightly to reflect the current understanding.
The class
Position
is the abstraction for an account's balance or position for particular instruments. The example is from the financial services domain where accounts are managed by the firm and updated with information from the trading and settlement systems.class Position {
private Account account;
private Instrument instrument;
private double balance;
//..
private PositionUpdationStrategy strategy;
//..
void doUpdate(..) {
strategy.doUpdate(..);
}
}
The method
Position#doUpdate(..)
updates the Position with the new information, which gets reflected in the state of the object. And we have a Specification class which validates if a Position
is short - this class encapsulates the domain knowledge for determining a short position for an account.class ShortPositionSpecification
extends PositionSpecification {
boolean isSatisfiedBy(Position pos) {
//..
//.. short position logic
//return true if short position
}
}
Now, here is a service class which uses the specification to determine short position and act accordingly.
class PositionManager {
//..
//..
Position updatePosition(..) {
Position current = getPosition(..);
current.doUpdate(..);
//.. invoke the specification
if (new ShortPositionSpecification().isSatisfiedBy(current)) {
// .. alternate business path
//.. do what ?
return null;
}
return current;
}
//..
//..
Position getPosition(..) {
//..
}
}
Now, we have a couple of issues here :-
- What do we return from
PositionManager#updatePosition(..)
if the position is short and we need to execute the alternate business logic ? Null ? Sounds yucky - explicit null checks need to be done by clients ofPositionManager
, which looks smelly. - One option may be to have a flag in Position which indicates short-position, and have it set when the Specification indicates short position. Once again the clients of
PositionManager
need to do an if-then-else check to determine the short position of the account and handle the alternate business logic. Yuck! Still smelly ..
Let us put our Position model on some more steroids. The domain language says that in case of existing delinquent positions, the method
PositionManager#updatePosition(..)
should bail out early without doing any updates. This adds yet another PositionSpecification
to our module ..class DelinquentPositionSpecification
extends PositionSpecification {
boolean isSatisfiedBy(Position pos) {
//..
//.. delinquent position logic
//return true if delinquent position
}
}
and add yet another check in
PositionManager#updatePosition(..)
:class PositionManager {
//..
//..
Position updatePosition(..) {
current = getPosition(..);
if (new DelinquentPositionSpecification().isSatisfiedBy(current)) {
//.. alternate business path
//.. do what ?
return null;
}
current.doUpdate(..);
if (new ShortPositionSpecification().isSatisfiedBy(current)) {
// .. alternate business path
//.. do what ?
return null;
}
return current;
}
//..
//..
Position getPosition(..) {
//..
}
}
Umm .. Things start getting a bit messy and procedural. In both the cases, we return null, since we do not have any typed abstraction indicating the alternate path of execution. Instead of nulls, we can think of implementing the Null Object pattern, NullPosition. While this will make things slightly better, still the client code has to rely on checking flags to determine the exact alternate pathway to take. The Specification pattern encapsulates the constraints (as it should be), but it does not provide any abstraction for identifying the alternate flow of control. The caller still has to process null returns and differentiate them based on some flags (which may be multiple). Here is what the client of
PositionManager
can do ..Position pos = PositionManager.getInstance().updatePosition(..);
if (pos == null) {
if (pos.isDelinquent()) {
//.. handle delinquent position
}
if (pos.isShort()) {
//.. handle short position
}
//.. any other ?
}
//..
//..
This is definitely not the OO way of handling things! Also, all the procedural if-then-else code violate the principle of Making Implicit Concepts Explicit, as specified by Eric Evans in his DDD book. The alternate business flow for short position or delinquent position is buried within the stream of if-then-else statements and flag manipulations. None of the abstractions publish any explicit contract that indicates the alternate pathway which our execution can take. Ideally we would like to have our apis publish all alternative paths explicitly, so that the api user can figure out all of them before programming-to-the-api set.
Can Polymorphism help ?
I can sense several fans of polymorphism raising their heads with the idea of returning polymorphic variants of Position from the
updatePosition(..)
method. This will never scale and we are bound to have an explosion of subclasses with this approach. Let us look elsewhere ..Flamebait : The love and hate of Checked Exceptions
In my last post, I had indicated how checked exceptions in Java help us provide typed abstractions publishing the alternate execution paths as part of the contract. And, as we saw above, Specifications allow us to encapsulate the business rule itself, whose violation leads to the execution of those alternate paths. Combine the two, and we can have a better typed model for both the cause and the execution of the alternate flows.
Move the invocation of the specifications to the source instead of checking them as post-conditions in
PositionManager#updatePosition(..)
..class PositionManager {
//..
//..
//.. explicit publication
Position getPosition(..)
throws DelinquentPositionException {
//.. find the current position
current = ..
if (new DelinquentPositionSpecification().isSatisfiedBy(current)) {
throw new DelinquentPositionException(..);
}
}
}
and short-position handling moves to the strategy ..
class DefaultPositionUpdationStrategy
implements PositionUpdationStrategy {
void doUpdate(Position position)
throws ShortPositionException {
//.. check for short position
if (new ShortPositionSpecification().isSatisfiedBy(position)) {
throw new ShortPositionException(..);
}
//..
}
}
Now
PositionManager#updatePosition(..)
becomes cleaner with lots of specification checking moving inside the guts of respective classes ..class PositionManager {
//..
//..
Position updatePosition(..) {
try {
//.. normal flow
current = getPosition(..);
current.doUpdate(..);
return current;
} catch (ShortPositionException spex) {
//.. short position handling
} catch (DelinquentPositionException dpex) {
//.. delinquent position handling
}
}
//..
//..
}
Does this look better than the procedural if-then-else code above ? Checked exceptions are typed contracts that the caller needs to honor and handle. When I am designing typed apis, I consider them to be my friend. There is a community out there divided on the benefits of checked exceptions - recently we have seen lots of flamebaits once again on this very topic. And there are enough examples to showcase on either side of the argument. I find checked exceptions very useful while handling alternate business logic paths in my application. There are many situations where checked exceptions look like getting into your way, resulting in too much try-catch-finally in your codebase. But in handling alternate business scenarios, checked exceptions serve the purpose of advertising explicit business rule violations to the caller. They are part of the api and appear in the Javadoc - the user of your api gets to be aware of all alternate business scenarios that the domain model handles. And this makes the implicit concept explicit.
26 comments:
I agree and am of the opinion that all business exceptions should be checked exceptions. This forces all callers to explicitly handle the conditions at compilation time and makes no assumptions about the caller. Coding successful flows in try blocks and exceptional flows in catch blocks results in much cleaner code that is easier to read, maintain, test, and debug. Violations of business rules are exceptional conditions, and exceptional conditions call for exceptional measures.
I recently introduced a hybrid into my apps a 'ExpectedRuntimeException'.
These represent business and validation exceptions which I do not want to handle through the whole calling level as needed with Checked Exceptions - but which I will handle different at the service level (e.g. servlet, session bean etc.).
Hi,
Yes i agree with the author that checked expections can really make sense when trying to throw domain business driven exceptions.
Thanks
Prashant
I'm wondering why:
if (...) {
} else if (...) {
} else {
}
is smelly procedural code
but
try {
} catch {
} catch {
}
is not.
Aren't there better ways of
doumenting alternate outcomes than using checked exceptions?
The author of Pojos In Action http://www.manning.com/crichardson/ advocates using status objects to indicate business rule violations. This reserves the use of exceptions for truly unrecoverable errors, as opposed to anticipated business rule violations that are reconcilable.
There are several reasons why the try-catch approach is more ideal:
(1) The compiler forces you to handle all exceptional conditions
(2) All the details about the error condition are encapsulated in an exception object and are immediately available to the catching code
I still think that anticipated exceptions should be checked exceptions and unrecoverable exceptions should be runtime exceptions.
1) The compiler guarantees the exception is handled, but there's no guarantee it's handled correctly
2) The procedural smell remains because the cascading catches have to be repeated by all callers.
Using try/catch as a branching mechanism is serverely limiting to the coder, compared to if/else and switches. You can't combine boolean expressions, for one. The order of the catch blocks is important depending on the exception class heirarchy. An important exception could be masked by the catch block of another one in the same heirarchy. Developers have the habit of logging exceptions as errors, which you wouldn't want to do in these types of scenarios, because the ops analysts don't need to see a stack trace because a checking account was overdrawn
Ingo:
Clap clap clap...
@Bruce:
With respect to the solution in question, the try-catch approach establishes an explicit contract for all flows, and this is implied within the PositionManager. If you are implemeting this manger, then it is in your interest to handle the exceptions correctly. In this case you would handle them by controlling the flow and could also log a WARN/INFO level message (excluding stack trace) to a log for reporting purposes. Callers to the manager do not need to repeat the catch blocks. I agree that a lot bad habits have been learned by programmers in terms of exception handling. It is important that we unlearn these bad habits and use exceptions more wisely. I think their use here is justified.
@Debasish,
This would appear to be the sequel of our little exchange a short while back :-)
Here's a little reshuffling and more.
@Jing:
Sure .. this is a sequel to the earlier one. I find this a compelling case for Java's checked exceptions. There are scenarios where checked exceptions come in your way and force u to have unnecessary try/catch blocks within your code. In this case, I think the main differentiator is the Javadoc - making alternate pathways part of the contract (checked exceptions) make them appear explicitly as part of your Javadoc. And combine that with proper naming of the exception (the Ubiquitous language) from the domain - and the user of your api has a clear understanding of which alternate pathways are being handled by the contract.
Cheers.
Having a cascade of catches to handle the diferent conditions you have to work on is just another form of switch statement or chained if's.
I thinks it's even worse because of the same reasons that we talk about on the previous post.
Warped,
All I can say is, if we'd used checked exceptions to handle alt biz rules in the project I'm on now, the code base would have rapidly turned into a ball of try/catch mud.
try {
enterDrinkingEstablishment()
catch (Under21Exception u21e) {
try {
enterDrinkingEstablishmentUsingFakeId();
catch (InsufficientFakeIdException ifie) {
goto jail;
}
@Bruce:
How are you handling alternate business scenarios in your project ? Will be useful information if u can share it. As I see it, handling them with if-then-else is worse, since the logic gets embedded within procedural non-obvious code - a violation of "Make implicit concept explicit".
Cheers.
@Bruce,
Yes, I can understand your problem there. But regardless of whether you choose to use the checked-exception, return-value, or Jing's type-based approach, you can still end up with a ball of mud. I would expect the checked-exception ball of mud to be a much smaller and smoother ball of mud though! Mainly because it enforces a stricter contract and encourages better OO practices. I agree that you can establish a more loosely enforced contract using a return value (or type) approach, but that increases the possibility of creating a more messier ball of mud with things like if(x || y) or if(x && y), or (!(x || y)), or (!(x && y)), ..etc, etc.
Debasish,
If some error occurs and it requires notification to the user who initiated the action, the lower level business object throws an unchecked exception containing the info to be returned back to the client. At or near the top level of the call stack is a try catch that handles transaction rollback and rendering of the error back to the client. This keeps most of the business implementation clean of error handling code, which is nice because too often, error handling can obscure what the main purpose of a method is doing.
We do have a lot of if-else and switch statements, to handle corner cases and odd exceptions to primary biz rules. I will admit some areas of the code is too procedural, and causing maintenance headaches, but the solution to that is typically refactoring to introduce strategy or command patterns, etc.
WarpedJavaGuy,
Is there a mis-conception that branching is non-OO and should be minimized at all costs? My understanding is that a number of GoF patterns solve the problem of too much duplicated branching logic. This duplication is usually eliminated using delegation and polymorphism.
I am all for design patterns and am not advocating that branching is not OO. I just think that return-value-type solutions do not 'encourage' better OO practices, merely because they result in procedural code that more often than not eventually requires refactoring. I think that checked exceptions by their very nature make you more inclined to adopt design patterns a lot earlier in the process.
Bruce :
You have mentioned about error handling. My main point of discussion was "handling alternate business paths", which may not necessarily be errors. And in such cases, just transferring the information upto the client is not enough. The application needs to take an alternative action to follow the other course of action.
e.g. in my example, if the balance check fails, we have an alternate path to follow - we need to deal with Short Positions (this condition is abstracted by ShortPositionSpecification). Now the question is regarding the action that the application needs to take when such a condition arises. And here I have suggested using checked exceptions. Would love to know how you are dealing with such scenarios. You have mentioned unchecked exceptions - in such cases how to u enforce that the caller will handle this scenario.
Cheers.
> I just think that return-value-type solutions do not 'encourage' better OO practices
I suspect you are correct, Warped.
> I think that checked exceptions by their very nature make you more inclined to adopt design patterns a lot earlier in the process.
We'll have to agree to disagree on this point.
To Dabasish,
I suppose I didn't quite get the scenario. I assumed the short position exception meant informing the client the transaction could not be completed due to insufficient funds. Having said that, I doubt our project would have adopted the exception (checked or unchecked) strategy for this type of scenario. It just might be the case that you're use of exceptions is a good fit for the business cases in your system. If it ain't broke don't fix it.
I thought exceptions "are slow" because they are not intended to be used for common execution paths.
A redesign using "double-dispatch" is probably a better idea.
Spring has http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/core/ReflectiveVisitorHelper.html
There are a bunch of others too...
The link to spring got chopped. Can you repost?
@Bruce: The link is http://static.springframework.org/spring/docs/2.0.x/ api/org/springframework/core/
ReflectiveVisitorHelper.html. I have split it into 3 lines - just concatenate for the whole URL.
@WarpedJavaGuy,
For the record, I most certainly was _not_ proposing "Jing's typed approach". :-)
If you read the sentences following the sample code in my blog post, you would realize I was trying (evidently not quite successfully) to show that the checked-exception-based branching is no different from the "typed approach", which I consider very un-OO.
@Debasish
Your point on javadoc, albeit a good one from documentation perspective, does not justify forcing the caller to handle a checked exception immediately.
@Jing:
Your point on javadoc, albeit a good one from documentation perspective, does not justify forcing the caller to handle a checked exception immediately.
When I say :
void doUpdate(Position position)
throws ShortPositionException;
the language gives me a couple of things :
a) It states explicitly that the method doUpdate() detects the alternate pathway for handling short positions. This makes the *implicit concept explicit*, as per Evans' DDD book. Embedding the logic within if-then-else will not do this and the situation worsens if we have multiple such paths.
b) The Javadoc for the class explicitly mentions about this exception - hence the user of the api is also aware of all such paths that the api can handle.
Cheers.
@Jing,
> For the record, I most certainly was _not_ proposing "Jing's typed approach". :-)
Yes, I realise that and only qualified it with your name as a reference to the alternative approach as published in your post. Just wanted to keep you in the loop :)
Alrighty then, I do believe that both the return-value and return-type approaches have the potential to discourage better OO practices. This is because they are a lot less 'relaxed' than checked exceptions, and as a result are much more open to misuse. The problem starts when their misuse is tolerated in the first instance. If you misuse them once, then you are likely to misuse them again a second time, and a third, and so on until your code suddenly becomes too complex and difficult to maintain. This can all be avoided of course if you don't tolerate their misuse. The problem is that their misuse is too easily tolerated. Misused or mishandled checked exceptions on the other hand are not as easily tolerated and tend to force you to rethink your design in the first instance.
Effective Java Programming, Item 39: Use exceptions only for exceptional conditions.
"A well-designed API must not force its client to use exceptions for ordinary control flow."
I've been on a (almost) DDD project with Hibernate and Spring, and came to the conclusion to treat business exceptions as checked exceptions, while adopting the unchecked strategy for all the others. This was done mainly to allow some validation on business objects also on presentation layer, but it definitely made sense.
There was no try-catch mud, and if you get into it with ONLY business exceptions, there's probably wrong somewhere.
Using the "Item 39" approach makes sense if you have performance issues, but adding a possible exit condition doesn't really reflect in an information on the caller side. The key part is that if a new business circumstance has to be introduced in the system, an Exception forces explicit management a little more than a return type.
Post a Comment