Guava is a well-known library for Java programmers. It has plenty of useful tools and one of them is a cache implementation. This entry is about providing a cache instance to our domain class and how we can manage this from different points of view.

Here is pretty simple example I am going to base this blog on:

public class LoggedUsersCache {
    public LoggedUsersCache(...) {...}
    public void addLoggedUser(String mail) {...}
    public int loggedUsersCount() {...}
}

Let say our cache holds entries of logged users. When a user logs into our application a proper entry based on user’s mail is created in the cache. The cache itself provides information about a number of users logged at the moment. For our example, we can assume LoggedUsersCache has some eviction time.

There are several options that can be chosen to use Guava cache instance in this example:

  • instantiate cache with hard-coded parameters inside your class,
  • pass bunch of parameters into users’ cache and use it to configure the internal one,
  • create cache in LoggedUsersCache based on CacheBuilderSpec,
  • use ready-to-go Cache instance.

So let’s have a quick ride through all of them.

Instantiate a cache with hard-coded parameters inside your class

This one is the simplest one, right? Not really. How could we test the class? If a cache has some long eviction time for entries then how long would it take to do a test suite for it? So hard-coded parameters are the situation that I will not consider. It does not exist. 😉 Move on to the next option.

Pass bunch of parameters into users’ cache and use it to configure the internal one

Next option we have is to pass all required parameters to the constructor of our LoggedUsersCache class, all of them gathered into a single configuration class:

public LoggedUsersCache(AppConfig configuration) {
    this.internalCache = CacheBuilder.newBuilder()
                                     .expiresAfterWrite(configuration.evictionTime)
                                     .build();
}

Looks promising since cache implementation is totally hidden in the class. There is a drawback, however. If we would like to parameterise the cache with some other setting then three different places need to be updated. At first, we add a new parameter in configuration file. Next, AppConfig requires a new field to hold a value of the parameter. And at the end ‘LoggedUsersCache’ must call proper builder method to configure internal cache with a new setting. It is not the flexibility we would like to have I guess. And fewer places where change is needed means fewer possibilities to introduce a bug in our code.

We could handle this with setting every possible parameter while building the internal cache inside our class. But because of that, we would need to define all parameters in an application’s configuration file explicitly or provide sensible defaults in configuration class. When in comes to testing, the situation is not so good as well. We have to maintain at least one modified copy of the main configuration if we would like to switch off the internal cache (e.g. to have some tests running faster). This could be solved way better I think.

Create cache in LoggedUsersCache based on CacheBuilderSpec

Right. So the question is do we need a custom class – as above – to provide configuration settings for cache? Well, the answer is obviously… no. There is CacheBuilderSpec contained in Guava library. This class is a form of a specification configuration parameters of a cache. An instance of this class is used by CacheBuilder class when producing a cache instance. With this solution constructor of LoggedUsersCache will look like the following one:

public LoggedUsersCache(CacheBuilderSpec specification) {
    this.internalCache = CacheBuilder.from(specification).build()
}

With this one, we have a class that holds our configuration settings and there is no need to create own one. Additionally, only customised parameters have to be provided, leaving the rest with default values. Moreover creation of a cache looks cleaner – it’s just one-liner in this case. A builder specification is created from a single string that can be provided in a configuration for an application. Thanks to this there will be just only one place to change if it is needed. Yet another good thing I have found here is a cache’s entry class can be made private since it is used by LoggedUsersCache only. When it comes to a cache loader, weigher and removal listener of Guava’s cache their customised instances could be provided in the constructor as arguments or some private implementations could be used.

One may argue that passing CacheBuilder instance would be a better solution. Well, maybe under some circumstances it would be true. However providing the builder directly creates a risk of changing its settings in LoggedUsersCache constructor. And if the instance is used somewhere else this can result in unexpected behaviour of other caches created using this builder.

Testing with using CacheBuilderSpec seems pretty easy as well. You can simply provide a test specification of the cache builder with some helper method or as a bean in case of integration tests. There is no need to build up a cache but for every test, it is created the same way in constructor based on provided configuration.

Use ready-to-go Cache instance

The last option considered in this post is to simply pass already configured and created cache instance to LoggedUsersCache. While this sounds good and has some advantages like users’ cache is not bothered with cache building this is not necessarily the ideal solution. For example, there is no possibility to set up a loader, weigher or removal listener in the class where they could be private ones. Another thing is a class of an entry in Guava’s cache – with this implementation such class would have to be a public one. With a poor design of an application, there is a risk provided cache instance will be used in another place unexpectedly.

This case makes testing a bit harder I think. We would need to manually provide Guava cache (prepare configuration and build an instance) and repeat it for every test case different from standard one. This may be a tedious task. Having a builder inside LoggedUsersCache only a proper specification has to be created and everything else happens internally.

Summary

So I have shown a couple (definitely not all) of possibilities how one could solve “injection” of Guava’s cache into a domain class. And of course, examples above are framework-agnostic. When using some framework you will find some other solutions for this or similar case. As you read through you probably noticed I prefer the third solution among others. It gives the most flexibility. And looks clean I think. Do you have other ideas how this could be handled? Just let me know in the comment below.

And one more thing before you leave 😉 I would say even such trivial case as the one described here can tell us something. Our little choices made on a daily basis in the code are important for overall shapes of systems/applications we are working with and we should be aware of the pros and cons of those choices. So keep in my mind this and the next time when you are going to write a setter/getter pair or would like to pass some class instance into another one just think “Do I really need this? Or maybe is there a better way to accomplish my task?”


0 Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.