Friday, November 30, 2007

Spring modules caching in HTTP session

In a previous post, I described how I used declarative caching of my Spring beans using Spring modules. This strategy works great for caching data that is global to the application but breaks down when trying to cache user specific data. Let me give you an example.

On the site, there's a feature that allows users to save a partially completed crossword puzzles so they can be resumed later. In the service layer, I have a bean that is responsible for encapsulating that puzzle saving logic, let's call it the puzzle saving bean. One of its method returns the number of saved puzzles for a particular user. When the user is logged in, this number is displayed in the side bar as a reminder that there are puzzles awaiting completion. Since the side bar is shown on every page, the service layer is called a lot which overloads the database. This is unacceptable.

Fortunately, Tapestry allows me to solve this problem elegantly by declaring a UI component property to hold the number of saved puzzles. By giving that property session scope, it gets preserved into the HTTP session between calls. This performs great but won't work because of staleness. Let me explain. Let's say in another part of the site, the user saves a puzzle. Unless I specifically notify the component that holds the session property to discard it, the number displayed in the side bar will be wrong. Plus that notification introduces undesired coupling between two unrelated components.

So, what if instead of caching the number in the session I were to use the (global) caching version of the puzzle saving bean? That would solve the staleness problem since cached values are discarded when data altering methods are called. However, it introduces another problem. The cache gets quickly filled with user specific data and that data competes with the global data for resources. So what is the solution?

Well, it is quite simple. I liked the fact that the caching data in the HTTP session is tied in with the user's activity. If the user is idle, the data gets passivated. If the user logs out, the data gets deleted. I also liked the simplicity and the abstractness that Spring modules caching offers. So I combined both. I implemented a org.springmodules.cache.provider.CacheProviderFacade that uses the HTTP session to store the caching data. This is really ideal. I can choose to inject the global or session cached service bean, depending on the situation, and none of the code using it has to change. However, I need to carefully pick which version to inject. For instance, the UI component responsible for saving and deleting saved puzzles must use the session cached bean if I want the cached number of saved puzzles to be discarded.

I posted the code on the springmodules mailing list so feel free to take a look.