Transaction meta data


As we saw, a transaction in Datomic is also an entity with a timestamp fact. Since it’s an entity as any of our own entities, we can even add more facts that simply share the entity id of the transaction:


Depending on our domain we can tailor any tx meta data that we find valuable to associate with some transactions. We could for instance be interested in “who did it” and “in what use case” it happened and create some generic attributes user and uc in an Audit namespace:

trait Audit {
  val user = oneString
  val uc   = oneString

Then we can assert values of those attributes together with a save operation for instance by applying an Audit meta molecule

Audit.user("Lisa").uc("survey") the generic Tx namespace:"Fred").likes("pizza").Tx(Audit.user("Lisa").uc("survey")).save

This could read: “A person Fred liking pizza was saved by Lisa as part of a survey”

Molecule simply saves the tx meta data attributes user and uc with the transaction entity id tx4 as their entity id:


Now we can query the tx meta data in various ways:

// How was Fred added?
// Fred was added by Lisa as part of a survey
Person(e5).name.Tx(Audit.user.uc).get === List(("Fred", "Lisa", "survey"))

// When did Lisa survey Fred?
Person(e5).name_.txInstant.Tx(Audit.user_("Lisa").uc_("survey")).get.head === dateX
// Who were surveyed?"survey")).get === List("Fred")

// What did people that Lisa surveyed like? 
Person.likes.Tx(Audit.user_("Lisa").uc_("survey")).get === List("pizza")

// etc..


If we insert multiple entities in a transaction, the transaction data is only asserted once:"Lisa").uc_("survey")) insert List(
  ("John", "sushi"),
  ("Pete", "burgers"),
  ("Mona", "snacks")


Similarly we can insert composite molecules composed of sub-molecules/sub-tuples of data - and some tx meta data: +
  .Tx(MetaData.submitter_("Brenda Johnson").usecase_("AddArticles")) insert List(
  // 2 rows of data (Articles) 
  // The 2 sub-tuples of each row matches the 2 sub-molecules
  (("Battle of Waterloo", "Ben Bridge"), ("serious", 5)),
  (("Best jokes ever", "John Cleese"), ("fun", 3))

“Get serious articles that Brenda submitted”:

m( + 
  .Tx(MetaData.submitter_("Brenda Johnson"))).get === List(
  (("Battle of Waterloo", "Ben Bridge"), 5)


Transaction meta data can be attached to updates too so that we can for instance follow who changed data in our system.


Now when we look at a list of Persons and what they like we can see that some likes were from an original survey and one is from a follow-up survey that Ben did: === List(
  ("John", "pasta", "Ben", "survey-follow-up"),
  ("Pete", "burgers", "Lisa", "survey"),
  ("Mona", "snacks", "Lisa", "survey")


It’s valuable also to have meta data about retractions so that we can afterwards ask questions like “Who deleted this?".

Single attribute

To retract an attribute value we apply an empty arg list to the attribute and update. Here we also apply some tx meta data about who took away the likes value for Pete:


We can follow the likes of Pete through history and see that Ben retracted his likes value in a survey follow-up:

Person(peteId).likes.t.op.Tx(Audit.user.uc)).getHistory.toSeq.sortBy(r => (r._2, r._3)) === List(
  // Pete's liking was saved by Lisa as part of survey
  ("burgers", 1028, true, "Lisa", "survey"),
  // Pete's liking was retracted by Ben in a survey follow-up
  ("burgers", 1030, false, "Ben", "survey-follow-up")

The entity Pete still exists but now has no current liking:

Person(peteId).name.likes$.get.head === ("Pete", None) 


Using the retract method (available via import molecule.imports._) we can retract one or more entity ids along with a tx meta data:

retract(johnId, Audit.user("Mona").uc("clean-up"))

The Audit.user("Mona").uc("clean-up") molecule has the tx meta data that we save with the transaction entity.

John has now ben both saved, updated and retracted:

Person(johnId).likes.t.op.Tx(Audit.user.uc)).getHistory.toSeq.sortBy(r => (r._2, r._3)) === List(
  // John's liking was saved by Lisa as part of survey
  ("sushi", 1028, true, "Lisa", "survey"), // sushi asserted
  // John's liking was updated by Ben in a survey follow-up
  ("sushi", 1030, false, "Ben", "survey-follow-up"), // sushi retracted
  ("pasta", 1030, true, "Ben", "survey-follow-up"), // pasta asserted
  // John's liking (actually whole entity) was retracted by Mona in a clean-up
  ("pasta", 1033, false, "Mona", "clean-up") // pasta retracted

The entity John now currently doesn’t exists (although still in history)

Person(johnId).name.likes$.get === Nil