Recently I’ve decided to start writing something related to some of my daily challenges as a software engineer and the first topic that popped into my mind was the not-new but often forgotten, and sometimes even unknown, specification pattern.
Basically, I picked up this topic because in one of my previous work positions, I’ve made use of such pattern to successfully design and implement an awarded real-time engagement solution in the telecom field, and now once again, a similar problem came up for my team to tackle and then I noticed most of my colleagues hadn’t heard about such pattern.
Essentially, this time the main goal is to push near real-time notifications to the customers (e.g. banks and partners) throughout a WebSocket channel or a REST API. At a glance no big deal, but things get interesting when we have a relevant volume of raw data flowing through the pipeline in which, according to certain criteria created by the customers, should first be matched and enriched before becoming notifications.
In order to accomplish the goal, technologies such as Java, Kafka, Cloud Foundry, Istio/Envoy, and above all, the software design need to be harmonized so that all bits and pieces can nicely fit together delivering an extensible, secure, scalable and resilient solution, that our customers can doubtlessly rely on.
Following the Specifications Paper by Eric Evans and Martin Fowler, the specification pattern is a software design pattern which can be applied to encapsulate business rules that define a desired object’s state. This is a very powerful way of decreasing coupling and increasing extensibility to select a subset of objects that match against certain criteria. These criteria can be combined using logical operators to then form what Eric and Martin call Composite Specification.
So inspired by this short definition, now imagine the following scenarios with criteria to match Foreign eXchange Transactions:
- sourceCurrency = “EUR”
- sourceCurrency = “EUR” OR sourceAmount > 500
- sourceCurrency = “EUR” AND sourceAmount BETWEEN 500 AND 1000
- (sourceCurrency = “EUR” AND sourceAmount BETWEEN 500 AND 1000) OR sourceCurrency = “USD”
Above we have four valid scenarios with gradual complexity that customers can perfectly create, using, for instance, a nice UI built with the handy QueryBuilderJS plugin. Having said that, below I’ll be only outlining the groundwork needed to store specifications using Hibernate, and then, how to turn them into Java 8 predicates in order to filter objects out of an in-memory stream of data in a fashion way.
Designing Hibernate entities
I am going to first throw out the class diagram showing the big picture from what the entities would look like and then will walk through the important bits to bear in mind.
As aforementioned, FxTransaction and its fields such as sourceCurrency, sourceAmount and others will be our primary domain fields. With this established, the first and basic entity we have to create is the one to map those fields and their data types in the entity called FieldMapping.
We can now proceed and create an abstraction called FieldContent, which’s nothing more than a content holder using Java generics and will have one concrete class for each FieldType according to the above enumeration. This is basically a Hibernate trick to strongly type and then store the desired value for a given field.
Note that the Specification entity has a children field of itself and this will allow nested specifications enabling us to achieve a very good degree of criteria complexity.
Creating Java Predicate
Once we have the groundwork in place to store the specifications, we can build the components that understand the DB model and we will then create the predicates.
By using the Java functional bits as the soul of this technique, our life gets a lot easier and, with no more than a few simple classes, we can turn our dynamically created specifications into predicates as follows:
Finally, we can rely on the above Predicates factory to then apply the created predicate to our stream of FxTransaction objects in order to select a subset of objects that have been matched as follows:
Other tests, demonstrating the previously mentioned scenarios, can be found in the DataMatchingTest class.
At a first look, the specifications paper may seem complex, requiring a bunch of effort to set everything up. But hopefully, you could see here it’s in fact quite simple to put the groundwork in place and start matching up. Also, I hope you could notice the value of this technique for in-memory data matching and how it can make your life a lot easier. Finally, I should say given specifications are not changing very often, we should then store the predicates in some sort of in-memory cache to reduce the latency.
Full sample code can be found here: https://github.com/carlosraphael/specification-pattern