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.@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
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?
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.
Simple example: given a :person/id 123, what other person information can I tell you?
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?
For example, a resolver to get the address.id values for a person, and another resolver to get address.* by id?
I don't follow. Given your hierarchy, wouldn't :person/id return person attrs and :address/id return address attrs?
to join between them, you would provide partial maps with the foreign keys
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?
Pathom is probably smart enough to figure that out, but I feel like I’m making the graph less connected rather than more connected…
(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)}))Your reference attributes just return a list of nested maps which provide the id needed for the other side of the join
there's no path concerns, because the information is provided by the other half of the path already
OK, that is what I’ve been doing, so I feel like I might not be totally off base…
Does :address/person_id need to exist in your model at all?
A query for that resolver would be similar to
[{:person/addresses
[:address/id]}]
A query that involved that resolver, yes
but it would need :person/id
right.
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…
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
Excellent, I’ll check those out!
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 (?, ?, ?)
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.
Use that if you have some way to turn an EQL query into a single sql query
it's also how you can connect different pathom processes together
making a multiservice connected graph
Batch queries seem like it’s going to have high utility for my situation… Excellent! Thanks you so much for you time!