Skip to content

Commit 5109a11

Browse files
committed
Update documentation for @BatchMapping
Closes gh-130
1 parent 0bb8f58 commit 5109a11

File tree

1 file changed

+126
-24
lines changed

1 file changed

+126
-24
lines changed

spring-graphql-docs/src/docs/asciidoc/index.adoc

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -288,33 +288,36 @@ default it is marked as `INTERNAL_ERROR`.
288288
[[execution-batching]]
289289
=== Batching
290290

291-
Given a `Book` and its `Author`, we can create one `DataFetcher` to load books and another
292-
to load the author for each book. This enables queries to select only the data they need,
293-
but when loading multiple books we end up loading the author for each book individually,
294-
which is known as the N+1 select problem.
291+
Given a `Book` and its `Author`, we can create one `DataFetcher` for books and another
292+
for the author of a book. This means books and authors aren't automatically loaded
293+
together, which enables queries to select the subset of data they need. However, when
294+
loading multiple books, the author for each book is loaded individually, and this is
295+
an issue known as the N+1 select problem.
295296

296297
To address the issue, GraphQL Java provides a
297298
https://www.graphql-java.com/documentation/v16/batching/[batching feature] that allows
298-
related entities, in this case the authors for all books, to be loaded together instead
299-
of individually. Here is an outline of that mechanism:
300-
301-
- At request time, an application can register a batch loading function in the
302-
`DataLoaderRegistry` for each request, that can load instances of a given entity such as
303-
`Author` from a set of unique keys.
304-
- A `DataFetcher` can access the `DataLoader` for the entity and use it to load the
305-
entity by its unique key.
306-
- The `DataLoader` does not load the entity immediately but rather returns a promise, and
307-
defers until it can use the batch loading function to load all related entities together.
308-
- The `DataLoader` also maintains a cache of previously loaded entities.
309-
310-
Spring GraphQL provides the `BatchLoaderRegistry` to store registrations of batch
311-
loading functions. This is given to the `ExecutionGraphQlService` that in turn uses
312-
it to make registrations in the `DataLoaderRegistry` for each request.
313-
314-
A `DataFetcher` can then look up a registered `DataLoader` and use it to load entity
315-
instances, and likewise a controller method can declare a
316-
<<controllers-data-loader,DataLoader argument>> to access the registered loader for the
317-
entity.
299+
related entities, such as the authors for all books, to be loaded together. This is how
300+
the underlying mechanism works in GraphQL Java:
301+
302+
- For each request, an application can register a batch loading function as a
303+
`DataLoader` in the `DataLoaderRegistry` to assist with loading instances of a given
304+
entity, such as `Author` from a set of unique keys.
305+
- A `DataFetcher` can access the `DataLoader` for the entity and use it to load entity
306+
instances; for example the author `DataFetcher` obtains the authorId from the `Book`
307+
parent object, and uses it to load the `Author`.
308+
- `DataLoader` does not load the entity immediately but rather returns a future, and
309+
defers until it is ready to batch load all related entities as one.
310+
- `DataLoader` additionally maintains a cache of previously loaded entities that can
311+
further improve efficiency when the same entity is in multiple places of the response.
312+
313+
Spring GraphQL exposes a `BatchLoaderRegistry` that accepts and stores registrations of
314+
batch loading functions. The `ExecutionGraphQlService` accepts the registry as input and
315+
uses it to make per request `DataLoader` registrations. A `DataFetcher` then looks up the
316+
`DataLoader` for an entity and uses it to load instances, or in an annotated controller,
317+
simply declare a <<controllers-data-loader,DataLoader argument>> to access the
318+
registered loader. Annotated controllers also support a
319+
<<controllers-batch-mapping,@BatchMapping>> that avoids the need to use `DataLoader`
320+
directly.
318321

319322
The Spring Boot starter declares a
320323
<<boot-graphql-batch-loader-registry,BatchLoaderRegistry bean>>, so that applications can
@@ -522,6 +525,99 @@ for fields under the Query, Mutation, and Subscription types respectively. For e
522525
----
523526

524527

528+
[[controllers-batch-mapping]]
529+
=== Batch Mapping
530+
531+
<<execution-batching>> addresses the N+1 select problem through the use of an
532+
`org.dataloader.DataLoader` to defer the loading of individual entity instances, so they
533+
can be loaded together. For example:
534+
535+
[source,java,indent=0,subs="verbatim,quotes"]
536+
----
537+
@Controller
538+
public class BookController {
539+
540+
public BookController(BatchLoaderRegistry registry) {
541+
registry.forTypePair(Long.class, Author.class).registerBatchLoader((authorIds, environment) -> {
542+
// how to load authors for the given author id's...
543+
});
544+
}
545+
546+
@SchemaMapping
547+
public CompletableFuture<Author> author(Book book, DataLoader<Long, Author> loader) {
548+
return loader.load(book.getAuthorId());
549+
}
550+
551+
}
552+
----
553+
554+
For the straight-forward case of loading an associated entity, shown above, the
555+
`@SchemaMapping` method does nothing more than delegate to the `DataLoader`. This is
556+
boilerplate that can be avoided with a `@BatchMapping` method. For example:
557+
558+
[source,java,indent=0,subs="verbatim,quotes"]
559+
----
560+
@Controller
561+
public class BookController {
562+
563+
@BatchMapping
564+
public Flux<Author> author(List<Book> books) {
565+
// ...
566+
}
567+
}
568+
----
569+
570+
The above becomes a batch loading function in the `BatchLoaderRegistry`
571+
where keys are `Book` instances and the loaded values their authors. In addition, a
572+
`DataFetcher` is also transparently bound to the `author` field of the type `Book`, which
573+
simply delegates to the `DataLoader` for authors, given its source/parent `Book` instance.
574+
575+
[TIP]
576+
====
577+
To be used as a unique key, `Book` must implement `hashcode` and `equals`.
578+
====
579+
580+
By default, the field name defaults to the method name, while the type name defaults to
581+
the simple class name of the input `List` element type. Both can be customized through
582+
annotation attributes. The type name can also be inherited from a class level
583+
`@SchemaMapping`.
584+
585+
A `@BatchMapping` method can be a
586+
{javadoc}/org/springframework/graphql/execution/BatchLoaderRegistry.RegistrationSpec.html#registerMappedBatchLoader-java.util.function.BiFunction-[mapped batch loading] function:
587+
588+
[source,java,indent=0,subs="verbatim,quotes"]
589+
----
590+
@Controller
591+
public class BookController {
592+
593+
@BatchMapping
594+
public Mono<Map<Book, Author>> author(List<Book> books) {
595+
// ...
596+
}
597+
}
598+
----
599+
600+
It is possible to use imperative method signatures too, i.e. returning `List<V>` or
601+
`Map<K, V>`, which can be useful when there are no remote calls to make.
602+
603+
`BatchMapping` methods support two types of arguments:
604+
605+
[cols="1,2"]
606+
|===
607+
| Method Argument | Description
608+
609+
| `List<K>`
610+
| The source/parent objects.
611+
612+
| `BatchLoaderEnvironment`
613+
| The environment that is available in GraphQL Java to a
614+
`org.dataloader.BatchLoaderWithContext`.
615+
616+
|===
617+
618+
619+
620+
525621
[[controllers-methods]]
526622
=== Handler Methods
527623

@@ -664,6 +760,12 @@ to locate it in the `DataLoaderRegistry`. As a fallback, the `DataLoader` method
664760
resolver will also try the method argument name as the key but typically that should not
665761
be necessary.
666762

763+
[TIP]
764+
====
765+
For straight-forward cases where the `@SchemaMapping` simply delegates to a `DataLoader`,
766+
you can reduce boilerplate by using a <<controllers-batch-mapping,@BatchMapping>> method
767+
instead.
768+
====
667769

668770

669771
[[controllers-graphql-context]]

0 commit comments

Comments
 (0)