Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java tip: Orthogonality by example

The principles of modular and maintainable design in Log4j

  • Print
  • Feedback

Page 4 of 5

Figure 5. JDBCAppender violating orthogonality

Bypassing precompiled statements

There is another reason that JDBCAppender's design is questionable. JDBC has its own template engine prepared statements. By using PatternLayout, however, the template engine is bypassed. This is unfortunate because JDBC precompiles prepared statements, leading to significant performance improvements. Unfortunately, there is no easy fix for this. The obvious approach would be to control what kind of layout can be used in JDBCAppender by overriding the setter as follows.

Listing 4. Overriding setLayout()

public void setLayout(Layout layout) {
   if (layout instanceOf PatternLayout) {
      super.setLayout(layout);
   }
   else {
      throw new IllegalArgumentException("Layout is not valid");
   }
}

Unfortunately, this approach also has problems. The method in Listing 4 throws a runtime exception, and applications calling this method may not be prepared to catch it. In other words, the setLayout(Layout layout) method cannot guarantee that no runtime exception will be thrown; it therefore weakens the guarantees (postconditions) made by the method it overrides. If we look at it in terms of preconditions, setLayout requires that the layout is an instance of PatternLayout, and has therefore stronger preconditions than the method it overrides. Either way, we've violated a core object-oriented design principle, which is the Liskov substitution principle used to safeguard inheritance.

Workarounds

The fact that there is no easy solution to fix the design of JDBCAppender indicates that there is a deeper problem at work. In this case, the level of abstraction chosen when designing the core abstract types (in particular Layout) needs fine-tuning. The core method defined by Layout is format(LoggingEvent event). This method returns a string. However, when logging to a relational database a tuple of values (a row), and not a string needs to be generated.

One possible solution would be to use a more sophisticated data structure as a return type for format. However, this would imply additional overhead in situations where you might actually want to generate a string. Additional intermediate objects would have to be created and then garbage-collected, compromising the performance of the logging framework. Using a more sophisticated return type would also make Log4j more difficult to understand. Simplicity is a very desirable design goal.

Another possible solution would be to use "layered abstraction" by using two abstract types, Appender and CustomizableAppender which extends Appender. Only CustomizableAppender would then define the method setLayout(Layout layout). JDBCAppender would only implement Appender, while other appender implementations such as ConsoleAppender would implement CustomizableAppender. The drawback of this approach is the increased complexity (e.g., how Log4j configuration files are processed), and the fact that developers must make an informed decision about which level of abstraction to use early.

  • Print
  • Feedback

Resources