Stomp

Open to extension, closed to modification

 

One of the central tenants of object-oriented development is that your packages should be open to extension, but closed to modification.  How do you write classes which are flexible and configurable for clients who, perhaps, don’t have access to the source code?

 

The most important aspect of a flexible system is one in which a client can plug-in a new implementation for any object.  To allow this, Stomp encapsulates the creation of all important objects into factories, and then instantiates the factories themselves via reflection, as defined in your runtime.prefs file.  In this way, clients can plug-in their own factories, and then implement the factory methods to plug-in different object implementations.

 

Extending Stomp.  Let’s imagine that you wanted to benchmark every query in your system in order to find those that were taking the most time.  In other words, you want to plug-in a new Query implementation, and add some functionality to the “execute” method.  How would you go about doing this?

 

First, you’d need to find the factory responsible for creating the Query in the first place.   Let’s intercept the JDOFactory.singleton ().newQuery () call.  First, write a subclass of JDOFactory, which does the work:

 

 

public class MyJDOFactory extends JDOFactory

{

        public Query newQuery (Class queryClass)

        {

                final Query query = super.newQuery (queryClass);

                return new Query ()

                {

                        public Object execute ()

                        {

                                Bench.mark ("execute query");

                                try

                                {

                                        return query.execute ();

                                }

                                finally

                                {

                                        Bench.print ("execute query");

                                }

                        }

 

                        ... define the other query methods, delegate to query ...

                };

        }

}

 

In this case, I’m just wrapping the normal query with an inner class which is going to start and stop a timer when “execute” is called.  Now, whenever any client calls JDOFactory.singleton (), I want the system to return MyJDOFactory instead.  To do this, I just change the setting in runtime.prefs:

 

<Prefs>

        <factory>

                <JDOFactory>MyJDOFactory</JDOFactory>

                <ServiceFactory>stomp.enhance.EnhancedServiceFactory</ServiceFactory>

                <WrapperFactory>stomp.enhance.EnhancedWrapperFactory</WrapperFactory>

                <TransactionFactory>stomp.enhance.EnhancedTransactionFactory</TransactionFactory>

                <JNDIProperties>stomp.util.JNDIProperties</JNDIProperties>

        </factory>

 

           

 

</Prefs>

 

Now, all runtime code, including the code already in existence in Stomp itself, will now use MyJDOFactory.  Notice how cleanly MyJDOFactory can be added to and removed from the system.  None of your other code needs to change.  Importantly, no Stomp code needs to change.  You write a very simple class that changes the Query implementation being returned by Stomp, and no one is any the wiser.

 

 

Another example.  Let’s say that you wanted to send yourself an email any time a transaction failed.  After a little digging you might hit upon the TransactionFactory as the place to plug-in your new code.  You’ll want to plug-in a new implementation for the TransactionWrapper currently being returned… your new class might look like this:

 

public class MyTransactionWrapper extends TransactionWrapper

{

            public MyTransactionWrapper (PMWrapper pm)

            {

                        super (pm);

            }

 

 

            public void reallyCommit ()

            {

                        try

                        {

                                    super.reallyCommit ();

                        }

                        catch (RuntimeException e)

                        {

                                    … email yourself the stack trace …

                                    throw e;

                        }

            }

}

 

How easy is that?  Now you have to make the TransactionFactory use this implementation.  Let’s extend TransactionFactory, override the “createTransaction” method, and return our new implementation.  Your new factory might look like this:

 

            public class MyTransactionFactory extends TransactionFactory

            {

                        public TransactionWrapper createTransaction (PMWrapper pm)

                        {

                                    return new MyTransactionWrapper (pm);

                        }

            }

 

Notice that this new Transaction will have all the same features as the old one… but now you get an email when something fails.  Nice.  All we have to do now is tell Stomp to use this new TransactionFactory wherever there is a TransactionFactory.singleton () call.  This method call may be in many lowlevel classes of Stomp, but that doesn’t matter.  You just need to change the correct pref in runtime.prefs (factory.TransactionFactory) and everything will work.

 

 

Using this pattern in your own code.  It’s surprising how many problems can be solved using this system.  For example, in my production environment right now, I’m using the following plug-ins:

 

-        a ServiceFactory which adds more information to the standard Stomp metadata object

-        a WrapperFactory which catches database IOExceptions and attempts to rerun the query a few minutes later

-        a TransactionFactory which writes a temporary table when a transaction occurs (for database change tracking)

 

I don’t know what functionality you’ll need to add, but there’s a good chance you’ll be able to do it without any changes to the Stomp code.

 

You can add this flexibility to your own code as well.  Just encapsulate the creation of important objects in a factory.  Add a static method (I use “singleton”) to the factory, which uses AbstractFactory.singletonFactory (String factoryName) to look up the factory implementation.  Now, make all your methods instance methods.  It’s a little extra typing up front, but you’ll be surprised how handy this indirection can be.