Dataloader vs. GraphQL-Batch

GraphQL::Dataloader solves the same problem as GraphQL::Batch. There are a few major differences between the modules:

The incentive in writing GraphQL::Dataloader was to leverage Fiber‘s ability to transparently pause and resume work, which removes the need for Promises (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.

Comparison: Fetching a single object

In this example, a single object is batch-loaded to satisfy a GraphQL field.

ruby 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.

ruby dataloader.with(Sources::Record).load(1)

Since the requested object is (eventually) returned from .load, Nothing else is required.

Comparison: Fetching objects in sequence (dependent)

In this example, one object is loaded, then another object is loaded based on the first one.

ruby 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 .synced.

ruby record = dataloader.with(Sources::Record).load(1) dataloader.with(Sources::OtherRecord).load(record.other_record_id)

Comparison: Fetching objects concurrently (independent)

Sometimes, you need multiple independent records to perform a calculation. Each record is loaded, then they’re combined in some bit of work.

ruby 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:

ruby Loaders::Record.load_many([1, 2]).then do |record, other_record| do_something(record, other_record) end

ruby # 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:

ruby record, other_record = dataloader.with(Sources::Record).load_all([1, 2]) do_something(record, other_record)