A couple of years ago, I watched this talk/video called “Are we there yet” by Rich Hickey. In it he sorts out a way of thinking about identity, value, state and time. That view feels quite natural and is maybe even close to reality as we perceive it. He moves on to persistent data structures and different techniques to get from one state to the next. How to progress from one immutable value to the next (e.g. STM). And multiversion concurrency control. Watch the video.
Following that reasoning, Clojure is a natural consequence. Interesting because of the ideas above, built in.
Moreover, when this thinking makes it into a database like Datomic, that’s even more interesting.
There is both a Clojure and a Java API to work with Datomic.
Here are my picks why Datomic is exciting:
- It has this clear model of how to think about identity, value, state and time.
- A read cache is replicated to every peer so it scales well for many readers. This matches many use cases I can imagine.
- Support for different physical storages, e.g. cloud based.
Misc nice to know
- Data is stored as datoms where a datom is a tuple like (entity, attribute name, attribute value, transaction).
- A transactor takes care of writing in ACID transactions.
- There is a query language which is based on Datalog.
The database, a value, isn’t that weird
With Datomic, when we request a handle to the database we get a value.
The database is like a log of changes, new values (and also delete instructions) are added. We always add stuff, never modify the old stuff. So it’s possible to choose any point in time and see what the database state is like.
That’s what we get when we ask for the database. An immutable point in history. So it is a value.
And of course, it’s not a value in the 42 sense. It’s a value in the sense that a composite of values is also a value.
Some Java samples
Here I’m trying out the Java API.
With the Datomic distribution comes the in memory (i.e. no disk storage) database I use below. Here’s how we create such a database:
String uri = "datomic:mem://hello"; boolean ok = Peer.createDatabase(uri);
We need a connection, here’s how:
Connection connection = Peer.connect(uri);
To be able to add data we must first set up the schema. What we say is that we have a hello-attribute in the my_ns namespace. It’s a string and its cardinality is one. Here’s setting up the schema:
Object tempId = Peer.tempid(":db.part/db"); List <?> tx = Util.list( Util.map( ":db/id", tempId, ":db/ident", ":my_ns/hello", ":db/valueType", ":db.type/string", ":db/cardinality", ":db.cardinality/one", ":db/doc", "A hello" ), Util.list( ":db/add", ":db.part/db", ":db.install/attribute", tempId)); connection.transact(tx).get();
Now we can add data to the database. :db/add is the command, then there’s an id, then the attribute name and last the value.
List <?> tx2 = Util.list( Util.list( ":db/add", Peer.tempid(":db.part/user"), ":my_ns/hello", "hello world")); connection.transact(tx2).get();
To query the database we must first get the database value. The syntax below gives us the state at the instant we execute that line. When we then access that database value it will never change.
Database database = connection.db();
To query the database we use the query syntax below. It’s a pattern matching thing. ?entity and ?value are unknowns. So it says look for ?entity and ?value. What’s between the inner brackets is the matching which matches everything that has the attribute name my_ns/hello. The result of the println statements will be something like “17592186045418” and “hello world”.
Collection<List<Object>> results = Peer.q( "[:find ?entity ?value :where [?entity :my_ns/hello ?value]]", database); List<Object> result = results.iterator().next(); Object entityId = result.get(0); Object value = result.get(1); System.out.println(entityId); System.out.println(value);
Check out the Datomic web site for nice documentation and tutorials.