In our last project we used a JUnit test to check consistency of all code concerned with Axon event upcasting. We think this is interesting as we basically unit test source code rather than class behavior. In this blog we describe how we implemented it and conclude with a short evaluation and we list alternative approaches.

Source code consistency

The system we were building is using the Axon framework (Axon Framework) and in Axon all events in the system end up in an event store to be used for event sourcing later. We for example defined “Persons” and “Organisations” where Persons work for Organisations. During development such entities change for example, they get a new property. Events such as “OrganisationUpdated” therefore become versioned. When new versions, in Axon terms revision, contain mandatory new properties we have older events in our event store without those properties. In Axon terminology they need to be upcasted by Java classes called Upcasters. Over time there will be several upcasters taking an event first from revision 0 to revision 1, then from 1 to 2 and so on until we reach the current one.

In our project we introduced a generic way of managing upcasters and we introduced a coding standard for it as follows. We have an event in Kotlin defining the current revision like this:

@Revision("4")
 data class OrganisationUpdatedEvent(val id: String, val
 name: String, …

Next we have a series of upcasters where the name immediately tells what it does like:

OrganisationUpdatedEventUpcasterNullTo1.java

And

OrganisationUpdatedEventUpcaster1To2.java 

Next, each of these upcasters contain code like this:

private static final SimpleSerializedType INPUT = new 
	  SimpleSerializedType(
		OrganisationUpdatedEvent.class.getTypeName(), "1");

Upcasters use this information to “recognize” if a particular revision of an event can be upcasted. Finally upcasters are Spring Components requiring a particular ordering, so we have code like this:

@Component
@Order(1)
public class OrganisationUpdatedEventUpcaster1To2

We need this ordering because Axon runs all events through a list of all upcasters it knows about at event replay time. Axon does not impose anything in terms of ordering that list but we want to apply upcaster “…NullTo1” before “…1To2” etc. Managing Axon upcasters over time will be another blog post to be written soon as there are a couple of alternative implementations all with different pro’s and con’s.

Let us get back to our current subject. To summarize, in our coding standard we have:

  • The current revision in an annotation on an event (e.g. “@Revision(“4”)”)
  • A series of classes with a particular name (e.g. …NullTo1, …1To2, ….)
  • Per upcaster an INPUT and OUTPUT revision (e.g. “1” and “2”)
  • The previous revision in an annotation on an upcaster (e.g. @Order(1))

Given an event with a Revision “4” this implies:

  • There should be 4 upcasters
  • The name of each upcaster (OrganisationUpdatedEventUpcaster1To2) contains 2 revisions that should match values specified in INPUT (1) and OUTPUT (2) constants
  • Likewise, the name of each upcaster (OrganisationUpdatedEventUpcaster1To2) contains the TypeName used in INPUT and OUTPUT (OrganisationUpdatedEvent)
  • The value in the “@Order” annotation should equal the revision used in the INPUT(1)

Now how do we know that this standard is properly implemented? A JUnit test would add value to catch mistakes that are easily made and easily overlooked at review time such as copy-paste errors:

  • Forgetting to change the type in INPUT and OUTPUT constants
  • Forgetting to update the @Order annotation

And other errors like omitting the upcaster in the first place.

The JUnit Test

The test verifying the implementation of our coding standard relies on reflection but that should not be a surprise. Our first job is to obtain all event classes. As all events are processed by methods annotated by either “EventHandler” or “EventSourcingHandler” Axon annotations, we get all those methods and then infer their input parameter using code like this:

Reflections ref = new Reflections(new ConfigurationBuilder()                                       .setUrls(ClasspathHelper.forPackage("…"))
.setScanners(new MethodAnnotationsScanner()));

Set<Method> allMethods =
		ref.getMethodsAnnotatedWith(EventHandler.class); 

And

Set<Class> allEventClasses = new HashSet<>();
allMethods.stream()
           .map(Method::getParameterTypes)
           .map(Arrays::asList)
                .forEach(allEventClasses::addAll);

Getting the current revision is relatively straightforward as it is a “Revision” Axon annotation on the event. We can do this by getting the annotation on the class and then getting the value out like this:

Annotation annotation = eventClass.getAnnotation(Revision.class);
String val = (String)Revision.class.getDeclaredMethod("value").
   invoke(annotation);

Our next job is to check all upcasters are indeed present. We do this by trying to load them by using:

Class.forName("….OrganisationUpdatedEventUpcaster1To2”);

Note we need to know the package but as we keep upcasters in the same package as the events we infer it from the event.

The third item to verify is the @Order annotation on the upcaster. We use techniques described above: we get the “Order” annotation from the upcaster class and gets its value.

The last and hardest item to verify is to check values of the INPUT and OUTPUT constants like this:

private static final SimpleSerializedType INPUT = new 
	  SimpleSerializedType(
		OrganisationUpdatedEvent.class.getTypeName(), "1");

Where “SimpleSerializedType” comes from the Axon framework and has a constructor:

public SimpleSerializedType(String typeName, String revision)

We are after the value of “revision” and “typeName” as specified in the “INPUT” constant. We can infer these values from the class as it is a Field. We used:

cls = Class.forName("….OrganisationUpdatedEventUpcaster1To2");
Field field = cls.getDeclaredField("INPUT");
field.setAccessible(true);

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, 
   field.getModifiers() &amp; ~Modifier.FINAL);

SimpleSerializedType value = (SimpleSerializedType)field.get(cls);
String revision = value.getRevision();
String typeName = value.getTypeName();
    …

Note we had to change both accessibility as well as the modifiers to get to the values we were after. If you want to use these techniques please do mind that not all java Security Managers will allow this, the default one for java 11, however, does although it gives a warning about modifying accessibility on the modifiers field.

Why did we build this test in the first place and what was the result?

One moment in the project we decided to add the ID of the current user to all events to enable full auditability. We already had thousands of events in the event store and decided that the current user’s ID would be mandatory.  So we had to write about 40 upcasters. Some of the events already had a revision so the addition was relatively repetitive. After a while we wondered if we did not make any copy-paste error and started to think about an automated (and repeatable) test. And it actually caught an error albeit just one: we forgot to change the TypeName in INPUT and OUTPUT constants of an upcaster that was copy-pasted.

Also worthwhile mentioning is that in an earlier stage of the project we suffered from a bug that would have been caught by our test. And finally our test has caught yet another error in a recent stage.

In general we believe our approach is worthwhile when introducing a new field in many places on behalf of a cross cutting concern.

Concluding

We have seen how consistent usage of a coding standard for Axon upcasters can be verified by using reflection in a JUnit test. Not all coding standards can be verified by using only reflection, of course. Tools like Sonar for instance are specifically built for this purpose. Trying to build Sonar rules for the use case presented above is therefor the topic of our next blog.

Trifork
Read what else Trifork is doing within for Enterprise Application Development on our website: https://trifork.com/enterprise-applications/