35.3. Handling Failure
The preceding design provides a solution for client-side caching of ProductDescription objects in a persistent file, to improve performance, and also to provide at least a partial fall-back solution if the external products service can't be accessed. Perhaps 10,000 products are cached in the local file, which may satisfy most requests for product information even when the external service fails.What to do in the case where there isn't a local cache hit and access to the external products service fails? Suppose that the stakeholders asked us create a solution that signals the cashier to manually enter the price and description, or cancel the line item entry.This is an example of an error or failure condition, and it will be used as a context to describe some general patterns in dealing with failures and exception handling. Exception and error handling is a large topic, and this introduction will just focus on some patterns specific to the context of the case study. First, some terminology:
- Fault
the ultimate origin or cause of misbehavior.- Programmer misspelled the name of a database.
- Error
a manifestation of the fault in the running system. Errors are detected (or not).- When calling the naming service to obtain a reference to the database (with the misspelled name), it signals an error.
- Failure
a denial of service caused by an error.- The Products subsystem (and the NextGen POS) fails to provide a product information service.
Throwing Exceptions
A straightforward approach to signaling the failure under consideration is to throw an exception.
Guideline Exceptions are especially appropriate when dealing with resource failures (disk, memory, network or database access, and other external services). |
[1] Checked vs. unchecked exception handling is not covered, as it is not supported in all popular OO languagesC++, C#, and Smalltalk, for example.
Suppose that the original exception (using Java as an example) is a java.sql.SQLException . Should a SQLException per se be thrown all the way up to the presentation layer? No. It is at the wrong level of abstraction. This leads to a common exception handling pattern:
Pattern: Convert Exceptions [Brown01] Within a subsystem, avoid emitting lower level exceptions coming from lower subsystems or services. Rather, convert the lower level exception into one that is meaningful at the level of the subsystem. The higher level exception usually wraps the lower-level exception, and adds information, to make the exception more contextually meaningful to the higher level.This is a guideline, not an absolute rule."Exception" is used here in the vernacular sense of something that can be thrown; in Java, the equivalent is a Throwable .Also known as Exception Abstraction [Renzel97]. |
[2] Resolving an exception near the level at which it was raised is a laudable but difficult goal, because the requirement for how to handle an error is often application-specific.
Consider the names of these exceptions: Why DBUnavailableException rather than, say, PersistenceSubsystemException ? There is a pattern for this:
Pattern: Name The Problem Not The Thrower [Grosso00] What to call an exception? Assign a name that describes why the exception is being thrown, not the thrower. The benefit is that it makes it easier for the programmer to understand the problem, and it the highlights the essential similarity of many classes of exceptions (in a way that naming the thrower does not). |
Exceptions in the UML
This is an appropriate time to introduce the UML notation for throwing[3] and catching exceptions.
[3] Officially in the UML, one sends an exception, but throws is a sufficient and more familiar usage.
Two common notation questions in the UML are:
- In a class diagram, how to show what exceptions a class catches and throws?
- In an interaction diagram, how to show throwing an exception?
Figure 35.9. Exceptions caught and thrown by a class.
[View full size image]
[4] Note that starting in UML 1.4, the notation for an asynchronous message changed from a half arrowhead to a stick arrowhead.
Figure 35.10 shows the notation, using the prior description of SQLException translated to DBUnavailableException as an example.
Figure 35.10. Exceptions in an interaction diagram.
[View full size image]
Handling Errors
One side of the design has been considered: throwing exceptions, in terms of converting, naming, and illustrating them. The other side is the handling of an exception.Two patterns to apply in this and most cases are:
Pattern: Centralized Error Logging [Renzel97] Use a Singleton-accessed central error logging object and report all exceptions to it. If it is a distributed system, each local singleton will collaborate with a central error logger. Benefits:
Also known as Diagnostic Logger [Harrison98]. |
Pattern: Error Dialog [Renzel97] Use a standard Singleton-accessed, application-independent, non-UI object to notify users of errors. It wraps one or more UI "dialog" objects (such as a GUI modal dialog, text console, sound beeper, or speech generator) and delegates the notification of the error to the UI objects. Thus, output could go to both a GUI dialog and to a speech generator. It will also report the exception to the centralized error logger. A Factory reading from system parameters will create the appropriate UI objects. Benefits:
|
Figure 35.11. Handling the exception.
[View full size image]