Fork me on GitHub
#datomic
<
2022-09-21
>
shane14:09:49

👋 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.

shane14:09:03

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".

shane14:09:29

{: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}

shane14:09:46

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.

shane14:09:10

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?

favila19:09:03

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)

favila19:09:47

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

favila19:09:13

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

favila19:09:39

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.

favila19:09:16

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.

favila19:09:04

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

shane19:09:46

: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.

shane19:09:59

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

pyry20:09:14

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

pyry20:09:54

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

favila20:09:05

d/index-pull also

👍 1
1