Fork me on GitHub

👋 hello - I have a question around how best to model a parent-child relationship where a field on the child is unique in the context of the parent.


For example - I would like a workspace name to be unique within an organization but different organizations can each have a workspace called "My workspace".


{:db/ident :organization/workspaces
  :db/doc "The workspaces in the organization."
  :db/valueType :db.type/ref
  :db/cardinality :db.cardinality/many
  :db/isComponent true}

 ; workspace attributes
 {:db/ident :workspace/organization+slug+topdown
  :db/doc "The composite identifier of the workspace."
  :db/unique :db.unique/identity
  :db/valueType :db.type/tuple
  ;; :organization/_workspaces doesn't work as a composite tuple
  ;; since it seems to be more pull query rather than schema syntax
  ;; but it feels like a better data model because it is top down 
  ;; and I can use isComponent on the parent and I think it makes 
  ;; for easier pull queries
  :db/tupleAttrs [:organization/_workspaces :workspace/slug]
  :db/cardinality :db.cardinality/one}

 {:db/ident :workspace/organization
  :db/doc "The parent organizaiton of the workspaces."
  :db/valueType :db.type/ref
  :db/cardinality :db.cardinality/one}

 {:db/ident :workspace/organization+slug+bottomup
  :db/doc "The composite identifier of the workspace."
  :db/unique :db.unique/identity
  :db/valueType :db.type/tuple
  ;; this works but I need to add a ref going bottom up and either 
  ;; lose the isComponent ref on the parent or have duplicate ref fields
  ;; on parent and child
  :db/tupleAttrs [:workspace/organization :workspace/slug]
  :db/cardinality :db.cardinality/one}


It really feels like I should model this top-down so that I get to use isComponent and have nicer pull queries - but all the examples of composite tuples need to use refs that are on the entity.


I don't think this is a particularly unusual requirement - how should I solve this the datomic way? should I not be trying to solve this in the schema and instead use a pred fn like unique-within-organization that I write myself?


Part of the problem is that datomic does not enforce that isComponent attrs are actually unique. (i.e. that there's always one-and-only-one :organization/_workspaces per workspace)


I would say in general that you should strive to orient the direction of refs so that the cardinality is one or as small as possible


if you keep that principle, then the tuple approach is more natural


however there are tradeoffs: the edges of a domain are less discoverable, you can't use isComponent + retractEntity to enforce lifetimes (although honestly you quickly outgrow how naive that is), and you can't use index-pull in lots of cases where you would want to.


another approach is you can use :db/ensure and an entity predicate to enforce the invariant, but you have to know when the application should include that check.


the advantage of that is you can use the shape you want and you don't need to keep another index for a relationship that potentially very rarely changes


:thinking_face: interesting - these points echo most of my concerns. > orient the direction of refs so that the cardinality is one or as small as possible .. then the tuple approach is more natural this is good to know - I was definitely thinking about this > retractEntity ... you quickly outgrow how naive that is I was worried about losing this - but maybe its not that big of deal then > you can't use index-pull in lots of cases where you would want to this is what I noticed but I realized I use pathom for the api with lots of batch resolvers and most of my pulls are really just pulling ids. So maybe this won't be an issue in practice. > you can use :db/ensure and an entity predicate to enforce the invariant yea I was headed down this path and I probably still need to do this but I was hoping to hand over things like "is unique" to the db.


Ok - I think I just need to try something and see how it feels: 1. model bottom-up, so children have a single ref to the parent 2. now I can add my composite tuples to check for uniqueness 3. leverage pathom to hopefully make up for loss of easy pull queries. 4. look into custom transactions to replace isComponent + retractEntity thanks for the feedback! first time using datomic for anything more than toy projects so this definitely helps!

Dustin Getz19:09:47

Is there a faster way to stream attrs out of Datomic Cloud than (d/q {:query '[:find ?e :where [?e :db/valueType _]] :args [db]}) , clever use of the datoms API for example? Fast in terms of time to first byte


There definitely could be, as d/q is eager. For instance, d/qseq would typically be better for streaming results.

👀 1
Dustin Getz20:09:12

ah of course, thank you


Could also try using the AEVT index with the datoms API.

Dustin Getz20:09:21

it would be a fullscan though unless i am missing a clever trick to skip ahead


d/index-pull also

👍 1