GraphQL::Dataloader
solves the same problem as GraphQL::Batch
. There are a few major differences between the modules:
Promise
s from promise.rb
; GraphQL::Dataloader uses Ruby’s Fiber
API. These primitives dictate how batch loading code is written (see below for comparisons).Fiber
s since 1.9, they still aren’t widely used.GraphQL::Dataloader
outside GraphQL.The incentive in writing GraphQL::Dataloader
was to leverage Fiber
’s ability to transparently pause and resume work, which removes the need for Promise
s (and removes the resulting complexity in the code). Additionally, GraphQL::Dataloader
should eventually support Ruby 3.0’s Fiber.scheduler
API, which runs I/O in the background by default.
In this example, a single object is batch-loaded to satisfy a GraphQL field.
With GraphQL-Batch, you call a loader, which returns a Promise
:
record_promise = Loaders::Record.load(1)
Then, under the hood, GraphQL-Ruby manages the promise (using its lazy_resolve
feature, upstreamed from GraphQL-Batch many years ago). GraphQL-Ruby will call .sync
on it when no further execution is possible; promise.rb
implements Promise#sync
to execute the pending work.
With GraphQL::Dataloader, you get a source, then call .load
on it, which may pause the current Fiber, but it returns the requested object.
dataloader.with(Sources::Record).load(1)
Since the requested object is (eventually) returned from .load
, Nothing else is required.
In this example, one object is loaded, then another object is loaded based on the first one.
With GraphQL-Batch, .then { ... }
is used to join dependent code blocks:
Loaders::Record.load(1).then do |record|
Loaders::OtherRecord.load(record.other_record_id)
end
That call returns a Promise
, which is stored by GraphQL-Ruby, and finally .sync
ed.
With GraphQL-Dataloader, .load(...)
returns the requested object (after a potential Fiber
pause), so no other method calls are necessary:
record = dataloader.with(Sources::Record).load(1)
dataloader.with(Sources::OtherRecord).load(record.other_record_id)
Sometimes, you need multiple independent records to perform a calculation. Each record is loaded, then they’re combined in some bit of work.
With GraphQL-Batch, Promise.all(...)
is used to to wait for several pending loads:
promise_1 = Loaders::Record.load(1)
promise_2 = Loaders::OtherRecord.load(2)
Promise.all([promise_1, promise_2]).then do |record, other_record|
do_something(record, other_record)
end
If the objects are loaded from the same loader, then .load_many
also works:
Loaders::Record.load_many([1, 2]).then do |record, other_record|
do_something(record, other_record)
end
With GraphQL::Dataloader, each request is registered with .request(...)
(which never pauses the Fiber), then data is loaded with .load
(which will pause the Fiber as needed):
# first, make some requests
request_1 = dataloader.with(Sources::Record).request(1)
request_2 = dataloader.with(Sources::OtherRecord).request(2)
# then, load the objects and do something
record = request_1.load
other_record = request_2.load
do_something(record, other_record)
If the objects come from the same Source
, then .load_all
will return the objects directly:
record, other_record = dataloader.with(Sources::Record).load_all([1, 2])
do_something(record, other_record)