Fork me on GitHub
Alex K15:12:15

Firstly, thanks @wilkerlucio for Pathom. Looks like a superpower in the right hands. I'm having a bit of trouble figuring out how best to make it work with Datomic. I'm new to both (and Fulcro too), so I've been battling quite the learning curve. I still don't think I understand things as intended, so please forgive any silly questions (and also the message length). Q1) One thing that's confusing me is flattening out to-one relationships in the results map. For example, lets say I've got an entity "type" Person with an attribute :person/full-name, and another entity type Club with attributes :club/name, and :club/manager (which is a ref to a Person). I write a resolver that takes a Datomic :db/id for a Person entity and returns the attributes for that entity.

(pc/defresolver person-by-eid [{:keys [db] :as env} {:keys [db/id]}]
  {::pc/input #{:db/id}
   ::pc/output [:person/full-name]}
  (d/pull db [:person/full-name] id))
If I want to include the manager's :person/full-name in the same level of the Club resolver's result map, I'd have to remap it to a made up attribute e.g. :club/manager-full-name so that Pathom's indexes don't contain incorrect information (a :db/id for a Person returning :person/full-name, and also a :db/id for a Club returning :person/full-name, the latter being the wrong entity type).
(pc/defresolver club-by-eid [{:keys [db] :as env} {:keys [db/id]}]
  {::pc/input #{:db/id}
   ::pc/output [:club/name :person/full-name]}
  (let [club      (d/pull db [:club/name :club/manager] id)
        full-name (d/pull db [:person/full-name] (get-in club [:club/manager :db/id]))]
    (assoc club :club/manager-full-name full-name)))
As opposed to
(pc/defresolver club-by-eid [{:keys [db] :as env} {:keys [db/id]}]
  {::pc/input #{:db/id}
   ::pc/output [:club/name :person/full-name]}
  (let [club      (d/pull db [:club/name :club/manager] id)
        full-name (d/pull db [:person/full-name] (get-in club [:club/manager :db/id]))]
    (merge club full-name)))
I'm looking to inline a ton of attributes in places, which means I'd be inventing a ton of extra attribute names doing things this way. This isn't really a problem, but I feel like I'm misunderstanding Pathom, and taking some hacky routes to do things that would otherwise be straightforward. Am I going about this the right way? Q2) At the moment I've got resolvers taking a Datomic :db/id and pulling all the attributes for that entity type. I've also got UUIDs (e.g. :person/id) on many of the entities for external use, which I'm currently translating to a :db/id via a resolver, e.g.
(pc/defresolver person-by-id [{:keys [db] :as env} {:keys [person/id]}]
  {::pc/input #{:person/id}
   ::pc/output [:db/id]}
  (let [eid (first (first (d/q '[:find ?e
                                 :in $ ?id
                                 :where [?e :person/id ?id]]
                               db id)))]
    {:db/id eid}))
Is that the best way to be doing that? It would mean three or more resolvers where there are additional unique attributes that can be used as lookup refs, e.g. :person/email. Q3) For following Datomic refs backwards (underscore in front of the attribute in d/pull), I take it I'd just be writing another resolver that takes a :db/id for an entity, and returns the attributes for the entity on the other end of the ref (the same as following a ref in the usual direction)?


hello @noxdeleo, thanks for the kind feedback. About the questions, lets go over those: Q1/Q2: datomic is peculiar in the modeling because any entity can have any attribute, so you end up having to choose between: 1. make everything generic, just a single id an pull everything (I don't know anybody that tried that). 2. use specific ids for each "type", so you narrow the available properties given a specific id type, this is the most common and since you already have that for external apis I suggest you also use then on the pathom api so the derived attributes can be more predictable. If you do that way, then your case for person/club can look like this:

(pc/defresolver person-by-eid [{:keys [db] :as env} {:keys [db/id]}]
  {::pc/input #{:person/id}
   ::pc/output [:person/id :person/full-name :club/id]}
  (let [res (d/pull db [:person/id :person/full-name {:club/_manager [:club/id]}] id)]
    (-> res
        (assoc :club/id (get-in res [:club/_manager :club/id]))
        (dissoc :club/_manager))))

(pc/defresolver club-by-id [{:keys [db] :as env} {:keys [db/id]}]
  {::pc/input #{:club/id}
   ::pc/output [:club/id :club/name :person/id]}
  (let [res (d/pull db [:club/id :club/name {:club/manager [:person/id]}] id)]
    (-> res
        (assoc :club/id (get-in res [:club/manager :person/id]))
        (dissoc :club/manager))))
(there are some missing pieces, to convert the ids)


so I think question 3 also goes in the same, does that makes sense?

Alex K17:12:05

Totally, thanks! When I was struggling with this initially, I toyed with the idea of making a pluggable Datomic reader, similar to what you have for GraphQL. Sadly I'm time-limited with my current project, but once it's out the door, I'd be happy to give something like that a go if you think it would be of value? I'd dumped a few thoughts in a note for when I got around to it: * It could possibly resolve a query with a single d/pull if all nested attributes in (:query env) were in the Datomic schema. Guess that's only really an issue over the wire though? * Datomic schema already has ref cardinality, so maybe the reader could automatically flatten to-one joins into the results map. * Datomic schema lacks ref range, so the reader couldn't know what attributes the entity on the other end of a ref has without more information. Something along the lines of seems like it could help in this regard, while not reducing Datomic's schema flexibility to something like GraphQL types. I think point 3 covers that peculiarity in Datomic you mentioned.


hello, thanks for the considerations, I think some bases to datomic would be nice, there are docs needed since so many people are trying to use this way


I think an advanced integration would be possible like we do on the GraphQL side, but that still needs some work to be fully featured. The auto flatten may not be a good idea because not every case is flattenable, hierarchies for example tend to not be possible, also if the entities share any attribute that might be a problem to flatten as well, so since its case sensitive I think it stays better as a manual thing


we need more exploration on this space for sure 🙂

Alex K18:01:14

Cool. I watched a video yesterday from this year's Conj that looks like it might also make for a nice integration: It's something I've been wanting to do with this latest project (I really like the idea of having a declarative domain model in one place and deriving other things from that). Plenty to think about and play with at any rate.