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 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.