Composites

Tests…

As we saw earlier, Entities are simply groups of facts that share an entity id:

The last fact is kind of a black sheep though since the :site/cat attribute is not in the Person namespace.

Avoid non-intrinsic pollution

Since entities can have attributes from any namespace we have a challenge of how to model this in our schema definiton. It would be quick and easy to just make a relationship from a Person namespace to the Site namespace:

object YourDomainDefinition {
  trait Person {
    val name  = oneString
    val likes = oneString
    val age   = oneInt
    val addr  = one[Addr]
    val site  = one[Site]
  }
  trait Addr {
    val street = oneString
    val city   = oneString
  }
  trait Site {
    val cat    = oneString
  }
}

Then we could easily build a molecule to get the Site category:

Person.name.likes.age.Site.cat.get.head === ("Fred", "pizza", 38, "customer")

Modelling-wise this is just not the best idea since we could easily end up making lots of redundant relationships from various namespaces to Site:

object YourDomainDefinition {
  trait Person {
    val site  = one[Site]
  }
  trait Company {
    val site  = one[Site]
  }
  trait Project {
    val site  = one[Site]
  }
  // etc...
  
  trait Site {
    val cat    = oneString
  }
}

Relationships to Site are simply not intrinsic to or a natural core part of neither Person, Company or Project. Littering non-intrinsic relationships to Site - and possibly other cross-cutting namespaces like Tags, Likes etc - all over the place quickly clutters and pollutes our domain model.

Instead we want to create a more “loose association” to Site. This is what Datomic allow us to do by letting an entity id tie any attributes together as we see in the list of facts at the top of this page.

Composite modelling

In Molecule we can model “associative relationships” - or “composites” with the ~ method:

m(Person.name.likes.age ~ Site.cat).get === List(
  (("Fred", "pizza", 38), "customer")
)

We make a composite molecule from two “sub-molecules” Person.name.likes.age and Site.cat.

The composite result set is a list of tuples with a sub-tuple for each sub-molecule.

Since in this case the last sub-molecule only has one attribute value “customer” a single value for that is returned. If it had 2 attributes we would get a sub-tuple for that too:

m(Person.name.likes.age ~ Site.cat.status).get === List(
  (("Fred", "pizza", 38), ("customer", "good"))
)

And so on..

m(Person.name.likes.age ~ Site.cat.status ~ Loc.tags ~ Emotion.like).get === List(
  (("Fred", "pizza", 38), ("customer", "good"), Set("inner city", "hipster"), true)
)

We can compose up to 22 sub-molecules (!) which should give us plenty of room to model even the most complex composite aspects of our domain.

…with expressions

“Which positive elder hipster customers like what?”

m(Person.name.likes.age_.>(35) 
 ~ Site.cat_("customer")
 ~ Loc.tags_("hipster") 
 ~ Emotion.like_(true)).get === List(
  ("Fred", "pizza")
)

The combinations are quite endless - while you can keep your domain model/schema clean and intrinsic!

Arity 22+ molecules

Since composites are composed of up to 22 sub-molecules we could potentially insert and retrieve mega composite molecules with up to 22 x 22 = 484 attributes!

Next

Bidirectional references…