The canonical model, an ace upon your sleeve

The canonical model pattern is an ace up your sleeve in order to open your libraries for functionality otherwise to be implemented in a tiresome, error prone and redundant way. As you settle upon a canonical model within your library, your library’s will be able to interact with any existing and yet to be implemented functionality on top of your canonical model, making your bits and pieces work together magically.

For example any conversion implemented from and to your canonical model (e.g. unmarshaling from JSON or TOML and marshaling back) can be utilized by any (sub-)system harnessing your canonical model.

Ace upon your sleeve

In some of the REFCODES.ORG libraries I utilized this pattern by providing a CanonicalMap (Javadoc) in the refcodes-structure(Javadoc) artifact which’s base functionality is used by other libraries:

CanonicalMap

As the CanonicalMap inherits from the Map interface, it is fully compatible with the Java collections framework.

The CanonicalMap acts as REDFCODES.ORG’s secret weapon utilizing its swiss army knife functionality within the refcodes-properties, refcodes-net and refcodes-rest artifacts:

  • Convert any type from and to a CanonicalMap instance
  • Play with your data structure as if it were a Map
  • Retrieve data structure sub-trees from or insert data structure sub-trees to a CanonicalMap instance (see below)
  • Convert from or to a unifed data structure consisting of Map types and Collection types as well as of primitives

Some theory

The CanonicalMap actually represents a Map type with keys of type String and values of type String. The interesting part is that the CanonicalMap introduces some conventions:

The CanonicalMap represents a hierarchical data structure representing the concepts of the map and the array (the nodes of the data structure) and primitives (the leafs of the data structure). The hierarchy is expressed by keys which represent paths, similar to the paths used to locate files in a file system.

Simply speaking, we have a key/value data structure, extending the keys with the semantics of a path in order to represent our hierarchical data structure.

A CanonicalMap’s runtime state could look as follows (using the key=value notation for the content of the CanonicalMap):

1
2
3
4
5
/name=Sample application
/database/url=jdbc:mysql://my_host/my:database
/database/user=admin
/database/password=secret
/server/port=5161

The above CanonicalMap instance represents a hierarchical data structure as such:

|
+- name="Sample application"
|
+- database
|  |
|  +- url="jdbc:mysql://my_host/my:database"
|  |
|  +- user="admin"
|  |
|  \- password="secret"
|
\- server
   |
    \- port=5161

Programming such a hierarchical data structure with the CanonicalMap looks as follows:

1
2
3
4
5
6
CanonicalMap theCanonicalMap = new CanonicalMapImpl();
theCanonicalMap.put("/name=", "Sample application");
theCanonicalMap.put("/database/url", "jdbc:mysql://my_host/my:database");
theCanonicalMap.put("/database/user", "admin");
theCanonicalMap.put("/database/password", "secret");
theCanonicalMap.putInt("/server/port", 5161);

The drawing below illustrates just some of the capabilities of the CanonicalMap and its subtypes:

CanonicalMap

Data entering the CanonicalMap is unified into path and value tupels. You can manipulate this data and export the according data structure again.

The fun part

Now having the dead simple CanonicalMap, REFCODES.ORG utilizes its power in other artifacts requiring flexibility regarding input and output data (serlaization and deserialization or marshaling and unmarshiling as well as type conversion) by extending the dead simple CanonicalMap.

The refcodes-properties artifact’s central building block is the Properties type, which actually is a sub-type of the CanonicalMap:

See the Dead simple Java application configuration blog post on how to use the refcodes-properties library.

The refcodes-rest artifact makes heavy use of the the HttpBodyMap type, which also is a sub-type of the CanonicalMap:

See the Bare-Metal REST with just a few lines of code blog post on how to use the refcodes-rest library.

Both of the above mentioned libraries make heavy use of data conversion, e.g. we convert from or to HTTP message bodies containing JSON or XML data, we convert from or to .properties being stored as YAML or TOML, JSON or XML and we convert from HTTP message bodies or our .properties to some Java type: Using sub-types of the CanonicalMap, all of this is possible without redundant conversion programming in those libraries.