Molecule overview

On this page we’ll quickly get an intuitive overview of how Molecule queries and transactions look like.

Pages in the side menu on the right go into more detail.

Db connection

An implicit connection to a database is needed to create and execute molecule queries.

Here’s an example of importing the molecule api and setting up the Datomic Peer in-memory database:

import molecule.datomic.api._
import molecule.datomic.peer.facade.Datomic_Peer._

implicit val conn: Future[Conn] = recreateDbFrom(SomeSchema)

Setup describes the various database setups that can be used with molecules.

Get tuples/objects/json

Molecules fetch data as a Future of tuples, objects or a json String.

Data is retrieved by building molecules of attributes from a namespace. Calling get on a molecule will fetch a Future with tuples of typed data from the database that match the molecule:

val names           : Future[List[String]]                 =
val namesAndAges    : Future[List[(String, Int)]]          =
val namesAgesMembers: Future[List[(String, Int, Boolean)]] =
// etc..

Data can also be returned as an object for each row of data that has properties matching the attributes of the molecule. Note the namespacings ben.Address and ben.Address.City.

for {
  // Single row/object
  ben <- Person.name_("Ben")
  _ = ben.age ==> 23
  _ = ben.gender ==> "male"
  _ = ben.Address.street ==> "Broadway"
  _ = ==> "New York"
  // Multiple rows/objects
  _ <- { person =>
      s"${} is ${person.age} yeas old and lives on " +
              s"${person.Address.street}, ${}"
    // "Ben is 23 years old and lives on Broadway, New York"
    // "Lisa is ..." etc...
} yield ()

Or we can fetch data as a json String with attribute and namespace names used as properties:

for {
  _ <- Ns.str.Ref1.int1 insert List(("a", 1), ("b", 2))

  _ <- ==>
      |  "data": {
      |    "Ns": [
      |      {
      |        "str": "a",
      |        "Ref1": {
      |          "int1": 1
      |        }
      |      },
      |      {
      |        "str": "b",
      |        "Ref1": {
      |          "int1": 2
      |        }
      |      }
      |    ]
      |  }
} yield ()

The outer structure (“data”) of the json String follows the specification of GraphQL for compatibility.


In Datomic, data is not deleted but instead “retracted” since all changes are accumulated. That makes it possible to go back and see what data was retracted and is no longer current. That’s why we say “CRUD” instead of CRUD.

// Save populated molecule"John").likes("pizza").age(24).save

// Insert multiple tuples of data using a molecule template insert List(
  ("John", 24, "pizza"),
  ("Lisa", 20, "sushi")

// Update one or more attributes of a given entity id

// Retract ("delete") entity

// Retract attribute value

Attribute types

Card-one attributes

name here is a card-one attribute with a single value ==> "Bob")

Card-many attributes

interests here is a card-many attribute with a Set of distinct values ==> List(
  "Bob", Set("Baseball", "Origami"),
  "Liz", Set("Painting", "Traveling", "Tae Kwondo")

Map attributes

Keyed card-many attributes, or “Map attributes”, are useful for i18n for instance

for {
  _ <- Phrases.greeting("en" -> "hello", "de" -> "hallo").save
  _ <- Phrases.greeting("en" -> "hello") ==> Map("en" -> "hello"))
  _ <- Phrases.greeting.k("de").get.ap(_.head ==> Map("de" -> "hallo"))
} yield ()

Mandatory, Optional, Tacit

Attributes can be

  • a mandatory value,
  • an optional value ($ appended), or
  • a tacit value ( _ appended) that is mandatory but not returned:
// name is mandatory
// age$ is optional
// isMember_ is mandatory but not returned (tacit)
val membersWithOptionalAge: Future[List[(String, Option[Int])]] =$.isMember_.get

Attribute data types

Cardinality one             Cardinality many                 Mapped cardinality many
-------------------         -------------------------        --------------------------------
oneString    : String       manyString    : Set[String]      mapString    : Map[String, String]
oneInt       : Int          manyInt       : Set[Int]         mapInt       : Map[String, Int]
oneLong      : Long         manyLong      : Set[Long]        mapLong      : Map[String, Long]
oneDouble    : Double       manyDouble    : Set[Double]      mapDouble    : Map[String, Double]
oneBigInt    : BigInt       manyBigInt    : Set[BigInt]      mapBigInt    : Map[String, BigInt]
oneBigDecimal: BigDecimal   manyBigDecimal: Set[BigDecimal]  mapBigDecimal: Map[String, BigDecimal]
oneBoolean   : Boolean      manyBoolean   : Set[Boolean]     mapBoolean   : Map[String, Boolean]
oneDate      : Date         manyDate      : Set[Date]        mapDate      : Map[String, Date]
oneUUID      : UUID         manyUUID      : Set[UUID]        mapUUID      : Map[String, UUID]
oneURI       : URI          manyURI       : Set[URI]         mapURI       : Map[String, URI]
oneEnum      : String       manyEnum      : Set[String]

Attribute values


// equality

// negation
Person.age.not(42) // or

// comparison

// null
Person.age() // or

// Word search





Person.age(42 or 43) // same as
Person.age(42, 43)   // same as
Person.age(List(42, 43))

AND: card-many attribute category has both a “restaurants” and a “shopping” value:"restaurants" and "shopping") ==> List("Ballard Gossip Girl"))

Input molecules

“Input molecules” awaits 1, 2 or 3 input values. Useful for re-use

val personsOfAge = m(

personsOfAge(23) ==> List("Bob"))
personsOfAge(24) ==> List("Liz", "Don"))

2 inputs + logic (and relationships)

val typeAndRegion = m(

// Social media communities in southern districts
typeAndRegion(("twitter" or "facebook_page") and ("sw" or "s" or "se"))


Card-one ==> List(
  ("Bob", 23, "5th Avenue") 


// flat ==> List(
  (42, "coffee"),
  (42, "sugar")

// nested*(InvoiceLine.item) ==> List(
  (42, List("coffee", "sugar"))


Relationship to the same Namespace type (Person -> Person) ==> ("Bob", "Liz"))


Relationships can be defined to go in both directions so that we can traverse a graph uniformly: ==> List(
  ("Bob", "Liz", "Dan"),
  ("Dan", "Liz", "Bob")
  // etc...


Attributes from different Namespaces that are not explicitly related can be associated by sharing the same entity id. We call molecules with associative relationships “Composite molecules”:

m("Bob") + Bar.status("regular")).save
m( + Bar.status) ==> ("Bob", "regular"))

Tx Bundle

Transaction bundles can atomically transact multiple operations/statements in one transaction:

  // retract entity
  // save new entity,
  // insert multiple new entities, 6)),
  // update entity

Tx meta data

Add meta data to the transaction entity itself about the transaction:"John").likes("pizza").Tx(Audit.user("Lisa").uc("survey")).save

We can then query for specific tx meta data

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

// When did Lisa survey John?
Person(johnId).name_.txInstant.Tx(Audit.user_("Lisa").uc_("survey")) ==> dateX)
// Who were surveyed?"survey")) ==> List("John"))

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

// etc..

Tx function

Ensure transactional atomicity with tx functions that run within a single transaction. If any part of the function throws an exception, the whole transaction is aborted.

Here’s a money transfer function where we want to be sure that both accounts are updated correctly:

// Pass in entity ids of from/to accounts and the amount to be transferred
def transfer(from: Long, to: Long, amount: Int)
            (implicit conn: Future[Conn], ec: ExecutionContext): Future[Seq[Statement]] = {
  for {
    // Validate sufficient funds in from-account
    curFromBalance <- Ns(from)

    _ = if (curFromBalance < amount)
    // Throw exception to abort the whole transaction
      throw TxFnException(
        s"Can't transfer $amount from account $from having a balance of only $curFromBalance."

    // Calculate new balances
    newFromBalance = curFromBalance - amount
    newToBalance <- Ns(to) + amount)

    // Update accounts
    newFromStmts <- Ns(from).int(newFromBalance).getUpdateStmts
    newToStmts <- Ns(to).int(newToBalance).getUpdateStmts
  } yield newFromStmts ++ newToStmts

We then call the transaction function inside a transactFn method:

transactFn(transfer(fromAccount, toAccount, okAmount))


Datomic has powerful ways of accessing all the immutable data that accumulates over time in the database:

// Current data

// As of some point in time - how did it look at that time?

// Since some point in time - what has happened after this time?

// History of all name operations - what names were added and retracted?

// Test what-if scenarios given some test statements - how will it look if we do x?

Generic APIs

Molecule provides access to Datomic’s various generic interfaces and apis.

Entity API

touch an entity id to get all it’s attribute values ==> Map(
  ":db/id" -> orderId,
  ":Order/lineItems" -> List(
      ":db/id" -> 102L, 
      ":LineItem/qty" -> 3, 
      ":LineItem/product" -> "Milk",
      ":LineItem/price" -> 12.0),
      ":db/id" -> 103L, 
      ":LineItem/qty" -> 2, 
      ":LineItem/product" -> "Coffee",
      ":LineItem/price" -> 46.0))


Retrieve generic data about entities and attributes:

// Entity id of Ben with generic Datom attribute `e` ==> (benEntityId, "Ben"))

// When was the Ben's age last changed? ==> (24, <April 4>)) // (Date)

// etc...



Attributes and values of entity e1

EAVT(e1) ==> List(
  (":Person/name", "Ben"),
  (":Person/age", 25),
  (":Golf/score", 5.7)


Values, entities and transactions where attribute :Person/age is involved

AVET(":Person/age") ==> List(
  (25, e1, t2),
  (23, e2, t5)
  (14, e3, t7),

// AVET index filtered with an attribute name and a range of values
AVET.range(":Person/age", Some(14), Some(24)) ==> List(
  (14, e4, t7),
  (23, e2, t5)


Entities, values and transactions where attribute :Person/name is involved

AEVT(":Person/name") ==> List(
  (e1, "Ben", t2),
  (e2, "Liz", t5)


Get entities pointing to entity a1

VAET(a1) ==> List(
  (a1, ":Release/artists", r1),
  (a1, ":Release/artists", r2),
  (a1, ":Release/artists", r3)


Data from transaction t1 until t4 (exclusive)

Log(Some(t1), Some(t4)) ==> List(
  (t1, e1, ":Person/name", "Ben", true),
  (t1, e1, ":Person/age", 25, true),

  (t2, e2, ":Person/name", "Liz", true),
  (t2, e2, ":Person/age", 23, true),

  (t3, e1, ":Person/age", 25, false),
  (t3, e1, ":Person/age", 26, true)


Get information about the Datomic schema which is also a way to access your Data Model programatically.

Schema.part.ns.attr.fulltext$ ==> List(
  ("ind", "Person", "name", Some(true), "Person name"), // fulltext search enabled
  ("ind", "Person", "age", None, "Person age"),
  ("cat", "Sport", "name", None, "Sport category name")