Modeling domain data with Clojure
17.05.2013
Permalink
Records in Clojure are a great way to get a more formalized
(and more performant) carrier for your domain data. As they
support the associative abstraction we don't loose the
flexibility of maps. But in order to use them to model business
domain related data something is missing...
Creating enterprise software systems always requires
some kind of domain data model or a data dictionary. Something
that formally describes how the domain data is structured and
what constraints apply. I've seen different approaches reaching
from using a spreadsheet (poor tool for that purpose, but allows
for prose description, filtering, searching and is often a preferred
tool for more business minded heads), to UML class diagrams
(much better with good visualization, but how can you take full
advantage of the semi-formal information, once you have it in your model?)
to formal textual models (SQL DDL, or based on grammars like XML-Schema
or individual external domain specific languages).
You need such a model for documenting what you have learned about
a domain in terms of data, and -- ideally -- to generate programming
artifacts (like Clojure records, database tables or Java classes) to
actually hold the data your program deals with.
The metamodel is in essence quite simple, it consists of
- Simple types, whose instances keep scalar values like
one string, one number or a date.
- Complex types, whose instances keep values for different
attributes, which are of simple type, complex type or a
collection type.
Beside that, one would likely benefit from a formal specification
of constraints that the instances of simple types or complex types
must conform to, and a validation routine to check on-demand if a
given data graph is -- in the sense of the constraints -- valid.
So in Clojure it would be nice to write a model down like this:
(defsimpletype km-distance "Non-negative distance in KM" number? (partial <= 0))
(defsimpletype required-string "A mandatory text of any length > 0" string? #(> (count %) 0))
(defcomplextype Trip [dist {:dt km-distance}
from-city {:dt required-string}
to-city {:dt required-string}
reason {:dt any :nillable false}]
#(not= (:from-city %) (:to-city %)))
(defcomplextype Expenses [trips {:dt Trip :card [0 2]}])
(defrecord SimpleRecord [foo bar])
It is not difficult to create Clojure functions and macros that
give you those features on top of records, so I created the small
library
domaintypes.
It additionally offers a Leiningen plugin that creates
a graphical representation of such a model using
PlantUML.
Feel free to play around with it. Feedback is welcome.