pathom

Scott Sutherland 2024-10-28T21:14:08.135409Z

Using Pathom3, I’m trying to build a multi-level, 1-to-many relationship query, and I suspect I have a fundamental misunderstanding when creating resolvers that allow this to happen. This discussion will be a little abstract, but I hope to solidify my base ideas for properly doing this. Generally, the levels can be considered subscription -> person -> address, each with an identifier. The data is being sourced from a database using honeysql, and I can quickly conceive SQL queries to walk this chain up and down. The idea of the query is to create a subscription-level view into this chain of data, so I’m thinking I can use an EQL query similar to the following:

[{:subscription
  [ subscription attributes...
   {:person
    [ person attributes...
     {:address
      [ address attributes... ]}]}]}]
and the graph should pull all the rows together, provided that there’s an access path from a subscription to a person and a person to an address. I’m going to post this to make sure that I’m still thinking about it reasonably. Then, I can 🧵 follow-up questions.

Eric Dvorsak 2024-11-01T14:55:24.907069Z

@scott.sutherland I have many relations like this in my app, the resolvers are generated like this https://github.com/yenda/fulcro-rad-sql/blob/develop/src/main/com/fulcrologic/rad/database_adapters/sql/resolvers_pathom3.clj#L154

Scott Sutherland 2024-10-28T21:41:06.597939Z

Assuming that I’m still in the ballpark with the previous idea, the next consideration is the creation of access paths. If there’s a person table with an id column, the address table has a person_id foreign key and an id column. One of the things that we would want to do is get the addresses for a person. I can write a resolver that takes a person.id value and runs a query on the address table where address.person_id == person_id and another resolver that takes an address_id and runs a query against the address table where address.id == address_id … However, based on https://clojurians.slack.com/archives/C87NB2CFN/p1727169864324879, it feels like I’m doing something wrong. That conversation suggests that we only want one resolver to the address attributes. If that’s the case, what are the best practices for working with multiple access paths in the graph?

favila 2024-10-28T21:52:12.520349Z

Pathom's basic model is one of information dependency: given a known input value with a certain semantic meaning, what output values can I derive/produce.

favila 2024-10-28T21:53:03.081019Z

Simple example: given a :person/id 123, what other person information can I tell you?

Scott Sutherland 2024-10-28T21:57:49.562899Z

OK. So, when creating a resolver that takes :person/id as input and provides :address/id :address/city, then I can create a resolver that runs that query. Generally, when connecting input values to output values, is it a better practice to create relationship resolvers and data resolvers?

Scott Sutherland 2024-10-28T21:58:13.303189Z

For example, a resolver to get the address.id values for a person, and another resolver to get address.* by id?

favila 2024-10-28T21:59:18.072699Z

I don't follow. Given your hierarchy, wouldn't :person/id return person attrs and :address/id return address attrs?

favila 2024-10-28T21:59:34.443719Z

to join between them, you would provide partial maps with the foreign keys

Scott Sutherland 2024-10-28T22:01:57.472079Z

OK, so the input for an address by person_id type query would take :address/person_id as input and provide :address/* attributes as output. If I create another resolver that takes :address/id and also returns :address/* have I broken the graph since there’s two ways to get to :address/city for example?

Scott Sutherland 2024-10-28T22:04:02.305929Z

Pathom is probably smart enough to figure that out, but I feel like I’m making the graph less connected rather than more connected…

favila 2024-10-28T22:06:24.801129Z

(pco/defresolver person-addresses
   [{id :person/id}] 
   {::pco/input [:person/id]
    ::pco/output [{:person/addresses [:address/id]}]}
   (let [address-ids (addresses-for-person-query id)]
     {:person/addresses
      (mapv (fn [aid] {:address/id aid}) address-ids)}))

favila 2024-10-28T22:06:50.355999Z

Your reference attributes just return a list of nested maps which provide the id needed for the other side of the join

favila 2024-10-28T22:07:19.570589Z

there's no path concerns, because the information is provided by the other half of the path already

Scott Sutherland 2024-10-28T22:08:28.520639Z

OK, that is what I’ve been doing, so I feel like I might not be totally off base…

favila 2024-10-28T22:08:35.421169Z

Does :address/person_id need to exist in your model at all?

Scott Sutherland 2024-10-28T22:09:20.142819Z

A query for that resolver would be similar to

[{:person/addresses
  [:address/id]}]

favila 2024-10-28T22:09:34.649679Z

A query that involved that resolver, yes

favila 2024-10-28T22:09:44.152419Z

but it would need :person/id

Scott Sutherland 2024-10-28T22:09:53.078369Z

right.

Scott Sutherland 2024-10-28T22:11:36.549129Z

OK, I think I’m thinking about it correctly. I find myself tempted to write deep table joins, which I think is against the idea of what Pathom is attempting to offer…

favila 2024-10-28T22:12:16.132579Z

The mapping of tables to graphs often produces n+1 queries along each "edge", which is maybe why you want to write those deep table joints. But the solution to that is either https://pathom3.wsscode.com/docs/resolvers#batch-resolvers or https://pathom3.wsscode.com/docs/dynamic-resolvers

Scott Sutherland 2024-10-28T22:13:08.319249Z

Excellent, I’ll check those out!

favila 2024-10-28T22:13:28.737149Z

batch resolvers give you every sibling at once, which lets you turn select X from Y where id=? to select X from Y where id in (?, ?, ?)

favila 2024-10-28T22:14:02.072589Z

Dynamic resolvers give you the whole portion of the query and value at once that the dynamic resolver says it can handle, and it must answer the whole result.

favila 2024-10-28T22:14:24.828969Z

Use that if you have some way to turn an EQL query into a single sql query

favila 2024-10-28T22:14:45.689599Z

it's also how you can connect different pathom processes together

favila 2024-10-28T22:14:59.902199Z

making a multiservice connected graph

Scott Sutherland 2024-10-28T22:15:30.195789Z

Batch queries seem like it’s going to have high utility for my situation… Excellent! Thanks you so much for you time!

1