Sonntag, 2. Oktober 2011

Java 7, Java 8 and Aspect Oriented Programming

The following code snippet is a method body from a SQL abstraction layer framework I'm working on.
The framework logic itself is near to irrelevant for this post, it's really all about the syntax.
All the method does is drop all columns identified as being obsolete from an existing table and add all newly defined columns yet missing in the existing table.
It does that by getting a Connection instance from a datasource managing a connection pool, executes the alterations through it, wrapping potentially occuring SQLExceptions in an exception type of this layer and finally (pun) gives that connection back to the datasource (the pool).
final int[] updateCounts;
final DbmsDatasource<H2DbmsAdaptor> datasource = this.sqlContext.datasource();
final Connection connection = datasource.getConnection();
Statement statement = null;
try{
    statement = connection.createStatement();
    this.addBatchDropColumns(table, obsoleteColumns, statement);
    this.addBatchCreateColumns(table, newColumns, statement);
    updateCounts = statement.executeBatch();
}
catch(final SQLException e){
    throw new SqlEngineException(e);
}
finally {
    JdbcUtils.closeSilent(statement);
    datasource.takeBack(connection);
}
This first code snippet is with Java 6 syntax. Get a connection and do the usual try-catch-finally dance with the JDBC resources. 14 Lines of code gross for 5 lines of net code. That's around 200% here or generally 9 LoC of overhead required not once nicely centralized but every time everywhere some SQL Operation has to be executed. Very much boiler plate code. The horror.

The next is the same snippet with Java 7 syntax-level. The new Automatic Resource Management (ARM) at least eliminates the annoying silent closing stuff and the blemish mutable Statement variable.
final int[] updateCounts;
final DbmsDatasource<H2DbmsAdaptor> datasource = this.sqlContext.datasource();
final Connection connection = datasource.getConnection();
try{
    try(Statement statement = connection.createStatement()){
        this.addBatchDropColumns(table, obsoleteColumns, statement);
        this.addBatchCreateColumns(table, newColumns, statement);
        updateCounts = statement.executeBatch();
    }
}
catch(final SQLException e){
    throw new SqlEngineException(e);
}
finally {
    datasource.takeBack(connection);
}
The major pain point, the every-time explicit exception handling boiler plate code and the pooled instance management remains. This is a typical example for what the Aspect-Oriented-Programming (AOP) guys call "cross-cutting concearn" or an orthogonal aspect of program code. While they still work on things like magically weaving cut points and point cuts and undebuggable whatevers that nobody actually wanted into bytecode and still tackling with problems like "doing it right would either blow memory or performance or both" (as I read somewhere in AOP articles), the same abstraction can be achieved utilizing functional programming.

What about shifting the cross cutting aspects of managing the pooled instance and the exception handling (both together or loosely connected as seperate modules) into a method accepting the net code as a modular function.
Like in the following method in the DbmsDatasource implementation:
@Override
public <R> R executeJdbc(final JdbcExecution<R> jdbcExecution)
{
    final Connection connection = this.getConnection();
    try{
        return jdbcExecution.execute(connection);
    }
    catch(final SQLException e){
        throw new SqlEngineException(e);
    }
    finally {
        this.takeBack(connection);
    }
}
This reduces the on-site code to this:
final int[] updateCounts = this.sqlContext.datasource().executeJdbc(new JdbcExecution<int[]>(){
    @Override public int[] execute(final Connection conn) throws SQLException {
        try(Statement statement = conn.createStatement()){
            Outer.this.addBatchDropColumns(table, obsoleteColumns, statement);
            Outer.this.addBatchCreateColumns(table, newColumns, statement);
            return statement.executeBatch();
        }
    }
});
Note that this is not just shifting boilerplate code into a method. It's shifting it from n on-site locations into 1 "aspect method" that only gets called in each of the n locations. So it doesn't really "count" when looking at the n code locations with the important
Of course the aspect could be even further abstracted into a dedicated function instead of a hard method, but that's not required in the example.
Anyway: using functional programming in that way is exactely what AOP is about: abstracting aspects like exception handling or resource management away from the business logic locations into a centralized point.

Currently, the anonymous inner class brings in the usual uglyness as a trade off. Still it's much less boiler plate code and more importantly: cleaner architecture.
And that will even go away when Java 8's project lambda will arrive (at last ^^):
final int[] updateCounts = this.sqlContext.datasource().executeJdbc(conn -> {
    try(Statement statement = conn.createStatement()){
        Outer.this.addBatchDropColumns(table, obsoleteColumns, statement);
        Outer.this.addBatchCreateColumns(table, newColumns, statement);
        return statement.executeBatch();
    }
);
Now, it finally looks like it should: hardly any boiler plate code, still everything is contained and properly in place: pooled instance management, exception handling, resource management - modularized, encapsulated where it should be but still fully controlable and debuggable. And of course the actual hand-taylored logic on-site.

Keine Kommentare:

Kommentar posten