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:
As the
CanonicalMap
inherits from theMap
interface, it is fully compatible with theJava 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 aCanonicalMap
instance - Play with your data structure as if it were a
Map
- Retrieve
data structure
sub-trees from or insertdata structure
sub-trees to aCanonicalMap
instance (see below) - Convert from or to a unifed
data structure
consisting ofMap
types andCollection
types as well as ofprimitives
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 thekeys
with the semantics of apath
in order to represent our hierarchicaldata 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:
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:
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.