Performance
The OTC framework has a great performance which was tested on a laptop with an 8GB RAM, 4 cores CPU running at 2.4GHz
running Java 8. The test consisted of copying just one single 'String' type field from a source object to a target
object. The performance was compared with Mapstruct, a know framework among the many existing object-mapping frameworke
in Java. It was an apples-to-apples comparison with the exact same operation executed across both the frameworks.
The performance comparison test was done using
Java Microbenchmark Harness (JMH) for throughput. And presented
below is a graphical representation of the results.
It shows various parameters but, I would like to draw your focus on the max throughput. The Mapstruct framework gave a
performance of around 0.9+ million OPS. But whereas the
OTC framework was more than eleven times faster giving a
performance of 10.1+ million OPS.
The JMH test classes used for both the tests are available as part of the download.
Unique Features
The OTC framework also stands out in its features and so thrives in making the life of a developer easy when it
comes to object transformations.
Before touching upon the unique features, let me introduce some common terms for some common ground -
-
Source and Target objects: As the objective is to convert one object to another, we have a source
object and a target object to deal with.
- Object-paths: Each of these object-types are tree structures which can have any depth and the values are
set on the leaf nodes. So we have a source-object-path and a target-object-path.
-
Notations:
- Dot notation: We will connect all the field-names in the object-path by a dot.
- Array/List notation: These object paths can have collections (arrays, lists, dictionaries).
So we will represent an array or a List with the "[*]" notation.
- Dictionary (Map) notation: We will represent a dictionary type collection with a
"[*,*]" notation.
- Key notation: A dictionary has a key-value pair. So we will represent the map-key with a
"[*,*]<k>" notation.
- Value notation: And we will represent the map-value with a "[*,*]<V>"
notation.
Here is an example -
travelers.traveler[*].anonymousTravelerMap[*,*]<V>.given[*].value
What we just saw is an example of an OTCS (Object Tree Convertor Script) object-path. If you have worked with
Spring's SpEL or OGNL, then this should look familiar to you. But OTC-Scripts as a whole (not just the object-path)
is more elaborate and has a different purpose and is central to the OTC Framework.
In the example we notice that the depth of the object-path is 6 levels. The first field-name
"travelers" is
the child of a parent object which is the root object (not shown here). So from the root to the leaf-node
"value" field, we have 6 levels. However there is no limit on the depth of the object-path that can be
represented.
With this understanding, now let me introduce you to the unique features -
- Complex Deep-object-paths with collections:
An object-path can have collections (arrays, lists, dictionaries which has key-and-value entries - aka. maps)
The generic types in collections (including the map-keys or the map-values) may be of a composite type with
each one extending the tree further down going deeper and thereby giving rise to complex deep object paths.
OTC handles all of them with ease.
- Mappings of disparate collection-types:
Both the object paths can have mixes and matches of disparate collection types. These mixes and matches in
each object-path can also be disparate across both the source and target object-paths. In simple words,
in OTC there are no restrictions to restrict same collection-types on both the source and target
object-paths. Say for example, if the source-object-path has a dictionary, there is no restriction that the
target-object-path as well should have a corresponding dictionary. Nor is there any restriction restricting
the number of collections to be equal across both the object-paths. Also, one may have a collection type while
the other may not have any collection-type at all for that matter. In such scenarios OTC applies some defaults.
- Permutations and Combinations:
Take a moment to consider the number of permutations and combinations of the source and target object-paths
that can arise. Deeper the object paths, more exponential are the mappings. The mixes and matches mentioned
above in Deep-object-paths can result in very high number of permutations and combinations.
- Collection correlations:
When both the source and the target object-paths have collections, and, if the target has a count more than
the collection-count than the source, the first collection in the source has to correlate with one of the
multiple collections in the target. OTC applies a default correlation in such scenarios.
- Anchoring:
This is one of the main unique highlights of the OTC framework. What if the default correlation explained
above is not applicable in your situation ?
Not to worry - OTC has a very unique feature called Anchoring. It allows you to override the default
correlation by just placing an Anchor character at the appropriate collection. Examples on Anchoring is
given under the below sub-title ("Examples on Anchoring") to give more clarity.
- Performance:
As already seen above, OTC delivers unmatched performance.
- No Annotations / Interfaces:
There is no need to create interfaces or induce any annotations.
- Fully automated:
There is no need to write lengthy configurations or code anything manually.
Examples on Anchoring
Note: XML was chosen in the below outputs for representation purposes only. But OTC works on any
object meant to hold data which could be "DTOs" or "Entity objects" or "JAXB objects"
to generate/consume XML or "JSON objects" to generate/consume JSON used in SOAP or REST services or the
"View objects" to integrate with the UI, etc.
Consider this object path -
dataLists.passengerList.passenger[*].identityDocuments[*].givenName[*]
This object-path has 3 collections -
'passenger[*]',
'identityDocuments[*]' and
'givenName[*]'.
Lets say, the framework creates an object as represented by the below output in its default behavior -
- This below output has a
- single passenger in the 'Passenger' collection and
- a single identityDocument in the 'IdentityDocument' collection
- but with multiple name in the 'GivenName' collection.
<DataLists>
<PassengerList>
<Passenger>
<IdentityDocument>
<GivenName>Rajesh</GivenName>
<GivenName>Prem</GivenName>
<GivenName>Malik</GivenName>
<GivenName>Mahesh</GivenName>
</IdentityDocument>
</Passenger>
</PassengerList>
</DataLists>
- But lets ask ourselves - will this be the case always ?
No. You might want to create an object with the values accommodated in a different way structurally by
stretching the 'IdentityDocument' and contracting the 'GivenName' as shown below -
<DataLists>
<PassengerList>
<Passenger>
<IdentityDocument>
<GivenName>Rajesh</GivenName>
<GivenName>Prem</GivenName>
</IdentityDocument>
<IdentityDocument>
<GivenName>Malik</GivenName>
<GivenName>Mahesh</GivenName>
</IdentityDocument>
</Passenger>
</PassengerList>
</DataLists>
- Or, as in this one with multiple 'Passenger' and each one with its own 'IdentityDocument' :
<DataLists>
<PassengerList>
<Passenger>
<IdentityDocument>
<GivenName>Rajesh</GivenName>
<GivenName>Prem</GivenName>
</IdentityDocument>
</Passenger>
<Passenger>
<IdentityDocument>
<GivenName>Malik</GivenName>
<GivenName>Mahesh</GivenName>
</IdentityDocument>
</Passenger>
</PassengerList>
</DataLists>
All you need to do is use OTC's Anchoring feature by placing the Anchor character appropriately on the object
path to switch between any of the stretching-contracting shown above and have the code generated in just few
milliseconds.