Some simple bits and pieces about Java EE architectures and Web solutions.

Wednesday, June 8, 2016

Triggering a Client Cache Refresh

More and more web sites are using a single page style architecture. This means there is a bunch of static resources (aka assets) including:

JS

CSS

HTML templates

Images

...

all residing in the client's browser. For performance reasons, the static content is usually cached. And like all caches, eventually the data gets stale and the cache must be refreshed. One excellent technique for achieving this in the web tier is Cache busting (see: here and here).
However, even using this excellent pattern there still needs to be some sort of trigger to indicate that things have gone out of date. Some options:

HTML5 Sockets

In this case a server can send a request to any client to say go and get the new stuff.

Comet style polling

The Client consistently polls the server in the background asking if there is a new version available. When the server says there is, the client is triggered to start cache busting.

Both of these are good approaches but in some edge cases may not be available:

Architecture may not allow HTML5 sockets - might be some secure banking web site that just doesn't like it.

The Comet style polling might be too intensive and may just leave open edge cases where a critical update is needed but the polling thread is waiting to get fired.

Client refresh notification pattern

Recently, on a project neither of the above approaches were available, so I needed something else which I shall now detail. The solution was based on some existing concepts already in the architecture (which are useful for other things) and some new ones that needed to be introduced:

The UI always knows the current version it is on. This was already burnt into the UI as part of the build process.

Every UI was already sending up the version it is on in a custom header. It was doing this for every request. Note: this is very useful as it can make diagnosing problems easier. Someone seeing weird problems, nice to be able see their are on a old version straight away.

The following new concepts were then introduced:

The server would now always store the latest client version it has received. This is easy to do and again handy thing to have anyway.

The server would then always add a custom header to every response to indicate the latest client version it has received. Again, useful thing to have for anyone doing a bit of debugging with firebug or chrome web tools

Some simple logic would then be in the client, so that when it saw a different version in response to the one it sent up, it knows there's a later version out there and that's the trigger to start cache busting! This should be done in central place for example an Angular filter (if you are using Angular)

Then as part of every release, just hit the server with latest client in the smoke testing.

As soon as any other client makes a request, it will be told that there is a later client version out there and it should start cache busting.

So the clients effectively tell each other about the latest version but without ever talking to each other it's all via the server. It's like the mediator pattern. But it's still probably confusing. So let's take a look at a diagram.

With respect to diagram above:

UI 1 has a later version (for example 1.5.1) of static assets than UI 2 (for example 1.5.0)

Server thinks the latest version static assets is 1.5.0

UI 1 then makes a request to the server and sends up the version of static assets it has e.g. 1.5.1

Server sees 1.5.1 is newer than 1.5.0 and then updates its latest client version variable to 1.5.1

UI 2 makes a request to the server and sends up the version of static assets it has which is 1.5.0

Server sends response to UI 2 with a response header saying that the latest client version is 1.5.1

UI 2 checks and sees that the response header client version is different to the version sent up and then starts busting the cache

Before the observant amongst you start saying this will never work in a real enterprise environment as you never have just one server (as in one JVM) you have several - true. But then you just store the latest client version in a distributed cache (e.g. Infinispan) that they can all access.

Note: In this example I am using two web clients and one back end server. But note the exact same pattern could be used for back end micro-services that communicate with each other. Basically anything where there is a range of distributed clients (they don't have to be web browsers) and caching of static resources is required this could be used.

1 comment:

About Me

Alex Staveley is a software professional passionate about software
engineering and technical architecture. He blogs about architectural
approaches, Java topics, web solutions and various technical bits and
pieces.