FSharp.Data.GraphQL
FSharp.Data.GraphQL copied to clipboard
[Discussion] Implement Per-Request Caching + Add an option for batching
Description
One of the biggest issues with GraphQL is that queries are very difficult to optimize, and may result in
-
Fetching the same data repeatedly
-
Inefficient fetches for lists
The query -> linq module provides some solutions for this, but it relies on all the data coming from the same source, and also requires a linq provider to be available for that data source. On top of that, there seems to be a lot of ✨magic✨ going on under the hood that makes it difficult for the user to optimize themselves.
Proposal
The proposal to fix this is three fold:
-
Implement a Haxl-like solution for batching + caching. I've implemented a port here.
-
Implement a default per-request caching strategy for all resolvers. I can't quite see any downsides to doing this, but this is open for discussion
-
Add a mechanism for the user to supply their own data sources, which allows them to define their own batching behaviour. This gives us the best of both worlds: Allowing some mechanism to make query execution more efficient, as well as providing flexibility for users to define the best way to do so.
I'm working on a Proof-Of-Concept right now, and all progress can be seen here. Let me know if anyone has any thoughts/concerns
Following up from a discussion on gitter, I'll see if I can take a stab at this over the next few days.
My plan is to create a Define.BatchField. Where the resolver's signature would look something like:
ResolveFieldContext -> seq<'Val> -> seq<'Val * 'Res>. The resolver would take as input the list of the parent values (instead of a single parent as with normal resolvers) and return a list of results grouped by parent.
e.g. from gitter the getHotelCreators' signature
getHotelCreators : ResolveFieldContext -> seq<HotelLocation> -> seq<HotelLocation * User>
Define.Object<HotelLocation>
(name = "Hotel", isTypeOf = (fun o -> o :? HotelLocation),
fieldsFn = fun () ->
[ Define.Field("id", String, "Identifier of the location.", fun _ (r: HotelLocation) -> r.Id)
Define.Field("name", String, "Name of the location.", fun _ (r: HotelLocation) -> r.Name)
Define.AsyncBatchField("creator", UserType, "Creator of the location.", fun ctx hotels -> getHotelCreator hotels)
) ])```
@johnberzy-bazinga This would make life great. Is there any help I can give for adding a Define.BatchField? I would use it everywhere.
@TOTBWF do you want to update your prototype to the lates code?
There is an example showing how to do batching here: https://github.com/fsprojects/FSharp.Data.GraphQL/issues/255#issuecomment-1640617865
I'm unsure if batching belongs in this library, since async provides a powerful customization point already.
I no longer work with F# nor GraphQL, so this prototype should be considered a historical artifact instead of live work. If anyone else wants to run with it, they are more than welcome to though!