First8 staat voor vakmanschap. Al onze collega’s zijn een groot aanhanger van Open Source en in het bijzonder het Java-platform. Wij zijn gespecialiseerd in het pragmatisch ontwikkelen van bedrijfskritische Java toepassingen waarbij integratie van systemen, hoge eisen aan beveiliging en veel transacties een belangrijke rol spelen. Op deze pagina vind je onze blogs.

Forward compatible messaging

If you have services or domains in your architecture, you probably have them to be able to develop them independently. In most cases, Message Queue’s are used to communicate between these services. But how does this work if one service upgrades the messages it is sending while the consuming services are still on an older version? In this blog post, we’ll dig into how Java Serialization deals with this.

Setup

To experiment, we’ll set up a Wildfly 9 container with a message queue. On it, we will deploy two domains or microservices, one that produces messages and one that consumes them.

First, setup a queue. Start Wildfly with standalone-full.xml profile so HornetQ is enabled.

Next, we’ll add a testQueue for our purposes.

Note: the second address contains jboss/exported which is required to make it accessible from the outside. You can leave it out if you want.

The code

Now, for some code. We will be sending an Event which looks like this:

Our producing side could look something like this:

And our consuming side uses a Message Driven Bean to consume the messages.

Testing forward compatibility

For our test, we deploy these two classes as separate WAR’s, bundled with their own Event class. Thus, this message is implemented twice, one in the producing domain and one in the consuming domain. You could also have a shared library between these domains where this class is placed. The same problem will arise if the producing domain upgrades to a newer version of that shared library.

Now, see what happens if we want to extend the Event class, exposing more information:

If we simply add fields but keep the serialVersionUID the same, the consuming side can still deserialize it, even when it has an old version of the Event class. If we upgrade the serialVersionUID, it will explicitly tell the runtime that the versions are incompatible and you will get a nice exception:

So we know that we can add fields without too much hassle. We could also delete fields. They will simply end up being null at the consuming side. There isn’t really a convenient way to set a reasonable default in those cases so be careful there. What else can’t we do?

  • change the package name, the class name or the superclass
  • changing the name or the type of a field

But what if you do need to e.g. change a field?

One strategy is to add a new field and fill out both versions of that field. Then, wait until all consumers have upgraded and have started to use the new fields. Then, you could safely delete the old field. E.g. if we want to change the payload from a String to an int, we could use this Event as an intermediate version:

 

Alternatively, you could write your own serializing mechanism (see e.g. Externalizable). You then can make up your own handling for these scenario’s, coping with forward compatibility somehow. Or you could switch to a different protocol or serializer that provide more forward compatibility or even cross-language capabilities. Some commonly used alternatives are kryo or protocol-buffers.

These days JSON is quite trendy so you could also consider to use a TextMessage instead of an ObjectMessage. The text could then be a serialized JSON object. If you want to go this way, you would need to tell the JSON serializer how to handle unexpected fields. In Jackson, you can e.g. add an annotation @JsonIgnoreProperties(ignoreUnknown=true) to get the same behaviour as with Java Serialization.

Read more: