Search This Blog

Adapter pattern: accesing Ehcache via Map interface

Suppose you have some client code (developed by you, legacy code or third-party library) which requires object of some specific type. On the other hand you already have almost exactly the same object, which does almost the same (in other words, fulfils similar contract). But the problem is, although both, required and yours, objects are pretty much the same, they have slightly or completely different interfaces. And in strongly typed Java world, even slightly means incompatible.

If you travel a lot, you probably came across the same problem in your real life. European and American AC power plugs and sockets are different. Although they both have same purpose – provide you with electrical power – and have similar contract (voltage, frequency, etc.), they simply don’t fit each other. Even though they are semantically equivalent, they are syntactically incompatible (have different shapes). The solution is the same for both developer and traveler: provide an adapter, which will have two interfaces: one fitting client (either Java code or your laptop power supplier) and one wrapping and translating target (your Java object or power socket found in local hotel).

OK, let’s go back to programming. As an example I have chosen Ehcache library, which is amazingly useful caching facility, mostly known as being Hibernate’s second level cache provider. But actually, Ehcache can be used in a variety of places, from a simple HashMap replacement to distributed, shared memory store with automatic peer discovery and disk persistence. If you have written some home-made caching solution and integrated it with your application, Ehcache should have been your first choice.

And here our problem arises. Look carefully at Map and Ehcache interfaces. They look similar, aren’t they? Both Map and Ehcache are data structures used to store data in key-value (dictionary) manner. It is tempting to use full-featured Ehcache instead of simple map and take advantage of automatic expiration, eviction and size/memory constraints. But since Ehcache interface does not extend Map, there is no direct way to achieve this. Of course, you may rewrite the code to use Ehcache instead of simple map, but in many cases it is impossible or too expensive (like your boss says, nothing’s impossible, only unprofitable). So you ask yourself, since the code requires me to supply any implementation of Map interface, maybe I can fool it and write special implementation, that only delegates to hidden Ehcache instance? In other words, this implementation won’t do almost anything by itself, instead only proxying and translating every call to Map methods to corresponding similar Ehcache operations. Yes, we are going to write implementation of Adapter pattern.

Figure above illustrates the design of our Ehcache adapter: it implements interface required by client code (Map), wrapping and hiding the target interface (Ehcache; sometimes called Adaptee). In Java we start by something like this:

The most important part is to implement Map interface in the way that does not violate the map contract. More generally speaking, Adapter must delegate to target in such way, that it behaves exactly the same as the client interface specifies – and same as any other implementation. In other words – if you have good unit tests for particular Map implementation, they should pass as well for the adapter. Here is the full source code:

Look carefully at any method – they mostly delegate to adaptee, but sometimes you must code a little bit to achieve equivalent functionality. I wrote a very small unit test to check that adapter implementation follows the map contract, though in order to be 100% sure, we should write at least couple test for every method independently. Please note that the test is written in Groovy and uses Groovy syntax – this shows that adapter can be safely used in any code which expects Map.

Final notes: our implementation has some additional benefit over using Ehcache directly: it is not only simpler, but also introduces strong-typing (Ehcache keys and values are of Object type). But to be precise, our adapter does not exactly conform to Map contract. It might happen, that value once put in map will not be present later even if it was not manipulated in the meantime. This is because the element might have expired and been removed from cache.

I hope you all get the idea of Adapter pattern. Maybe some of you have been using this pattern not knowing about that. For example I have been working with IBM WebSphere MQ message broker, which is available for Java developer via JMS adapter. I could use message driven beans, Spring’s JmsTemplate etc. just because MQ did provide their implementation of ConnectionFactory and queues. It was much easier to integrate it in existing app, rather than using vendor specific API. Think of Adapters before you rewrite legacy code or look for existing ones (like JDBC from MS Excel bridges).

Labels

Comments

Unfortunately, it doesn't look like your adapter meets the Map interface contract for the following methods:

* keySet()* values()* entrySet()

This is because according to the interface contract, the collections returned by these methods must all exhibit "write-through" behavior. However in your implementation they do not because you are returning new collections.

In my implementation the adapter returns copy (snapshot) of the current contents of the underlying cache. It should rather return cache-backed custom implementations.

The API also states that changes made to the map contents should be reflected by the returned collections (views). So - lots of things to be fixed, enough to write another article explaining how the contract was broken and how to fix that. Thank you once again!