| On local it's customer, but I renamed it Company for you and forgot to rename it on the bridge. I will call it Customer below. When I say cached result I misspoke, I mean Pathom, rather than running the one resolver that gets everything, will first see the resolver that gets only the user and grab that. Then it will consider :user/email satisfied and look for other resolvers. It seems to not look for the resolver that solves all the requested data, and instead goes first come first serve. > In the first case, the situation looks largely the same. I mean you can either uses the same nested inputs strategy but have a special key that triggers the fat-query resolver. Something like :employed-user-id. > The other alternative is using nested outputs. You have the resolver returned a keyed bundle. Something like {:fat-request [:users/email :company/phone_number]}. The downstream resolver then consumers a :fat-request and unpacks it using nested inputs. > You can make resolverZZ output some dummy key :zz-was-run. If you request :zz-was-run then I think it should also take the branch? (or maybe it runs both branches) I think that these tricks can work sometimes, but you might be surprised what odd behavior can come up if you rely on this. In your pathom query, the order of your requested data matters. (pco/defresolver get-user [{:users/keys [id]}]
{::pco/output [:users/id :users/email :users/customer_id :users/budget]}
(println "get-user triggered")
(-> (jdbc/execute-one! ds ["SELECT * FROM users WHERE id = ?" id])))
(pco/defresolver get-customer [{:customers/keys [id]}]
{::pco/output [:customers/id :customers/billing_number :customers/phone_number]}
(println "get-customer triggered")
(-> (jdbc/execute-one! ds ["SELECT * FROM customers WHERE id = ?" id])))
(pco/defresolver get-user-with-customer [{:users/keys [id]}]
{::pco/output [:users/id :users/email :users/customer_id :fat-key
:customers/id :customers/billing_number :customers/phone_number]}
(println "get-user-with-customer triggered")
(let [user (jdbc/execute-one! ds ["SELECT * FROM users WHERE id = ?" id])
customer (jdbc/execute-one! ds ["SELECT * FROM customers WHERE id = ?" (:users/customer_id user)])]
(merge user {:customers/id (:customers/id customer)
:customers/billing_number (:customers/billing_number customer)
:customers/phone_number (:customers/phone_number customer)
:fat-key true})))
(def user-customer-bridge
(pbir/alias-resolver :users/customer_id :customers/id))
;; ---- test1
(p.eql/process env
{:users/id 1000}
[:fat-key
:customers/phone_number
:users/email])
;; ---- get-user-with-customer triggered
;; ---- test2
(p.eql/process env
{:users/id 1000}
[:customers/phone_number
:fat-key
:users/email])
;; ---- get-user triggered,
;; ---- get-user-with-customer triggered
I couldn't tell you why it shakes out this way. Something about the bridge and requesting `:customers/phone_number` first must be coercing Pathom to take the longer path to :customers/phone_number, even though the other resolver can get everything in one shot. This is the kind of subtle behavior that is unintuitive and could blow up production if you trigger the wrong resolver in the wrong hot path. So I don't see a reasonable argument for using Pathom in an application that needs reliability unless you make the resolved paths part of the test suite. Although, I still think it could be a powerful combination with something like Sqlite or Datalevin where many reads don't matter as much. |
But I can't replicate the behavior. Here is the code:
https://github.com/kxygk/ednless/blob/master/pathom-prectest...
I didn't know what jdbc was so I just plugged in dummy values - but I think I'm matching your logic one2one
Let me know if you can tweak it to have the weird results trigger
At a high level I'd say a couple of things stand out.
- Your resolvers take in an id and return an id.. That sets off a bunch of alarm bells for me. I wouldn't ever do that. There is no good reason to have that, even if the values are identical (if the values change then that's even more dangerous). I can't point to exactly what will go wrong, but you're exacerbating the problem of having multiple resolvers providing an input. Worse yet, in this example you are guaranteed that they are all running. So where is it going to take the ID from..? I don't even know
- The :fat-key isn't doing anything here. If you wanted to do the nested request, it'd be a solid way to guarantee the fat branch is always taken:
I think you can see you get the same results, but there is no way for it to go wrong. Admittedly here I have the EQL request "unpacking" it, but it can also be unpacked transparently from a different resolver using nested inputs. This is very explicit and about equivalent to your original thought of "why not just have explicit imperative function calls". The consumer (the `fat-eater` or the user making the EQL request) knows a priori that he wants the fat call.EDIT: At the bottom of the .clj I added a bunch of other tests for the pipeline branching (the A->B->C thing)
- Adding an XXX resolver to override step B worked when "forced" with an extra input key.
- Adding an YYY resolver to override step B by outputting an extra key ends up always overriding B (whether you request the new dummy output key or not!)
I'm guessing here.. but it's probably b/c YYY has the same input requirements as B, but YYY outputs two keys instead of one - so it's preferred.
In a way both seem useful. The first lets you use a flag to select when you take a branch. The second make you always override the branch