最新の安定バージョンについては、Spring GraphQL 1.3.5 を使用してください! |
リクエスト実行
ExecutionGraphQlService
は、GraphQL Java を呼び出してリクエストを実行するための主要な Spring 抽象化です。HTTP などの基礎となるトランスポートは、リクエストを処理するために ExecutionGraphQlService
に委譲します。
主な実装である DefaultExecutionGraphQlService
は、起動する graphql.GraphQL
インスタンスにアクセスするための GraphQlSource
で構成されています。
GraphQLSource
GraphQlSource
は、使用する graphql.GraphQL
インスタンスを公開するための契約であり、そのインスタンスを構築するためのビルダー API も含まれています。デフォルトのビルダーは GraphQlSource.schemaResourceBuilder()
から入手できます。
Boot スターターはこのビルダーのインスタンスを作成し、それをさらに初期化して、構成可能な場所からスキーマファイルをロードし、GraphQlSource.Builder
に適用するプロパティを公開し、RuntimeWiringConfigurer
Bean、GraphQL メトリクスの計測 (英語) Bean、および例外解決用の DataFetcherExceptionResolver
および SubscriptionExceptionResolver
Bean を検出します。さらにカスタマイズするには、GraphQlSourceBuilderCustomizer
Bean を宣言することもできます。例:
@Configuration(proxyBeanMethods = false)
class GraphQlConfig {
@Bean
public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
return (builder) ->
builder.configureGraphQl(graphQlBuilder ->
graphQlBuilder.executionIdProvider(new CustomExecutionIdProvider()));
}
}
スキーマリソース
GraphQlSource.Builder
は、1 つ以上の Resource
インスタンスを使用して構成し、解析およびマージすることができます。つまり、スキーマファイルはほぼすべての場所からロードできます。
デフォルトでは、Boot スターターは、場所 classpath:graphql/**
(通常は src/main/resources/graphql
) で拡張子が ".graphqls" または ".gqls" のスキーマファイルを探します。ファイルシステムの場所、または Spring Resource
階層でサポートされている任意の場所を使用することもできます。これには、リモートの場所、ストレージ、メモリからスキーマファイルをロードするカスタム実装が含まれます。
classpath*:graphql/**/ を使用して、複数のクラスパスの場所にまたがるスキーマファイルを検索します。複数のモジュールにわたって。 |
スキーマの作成
デフォルトでは、GraphQlSource.Builder
は GraphQL Java SchemaGenerator
を使用して graphql.schema.GraphQLSchema
を作成します。これは通常の使用では機能しますが、別のジェネレーターを使用する必要がある場合。フェデレーションの場合、schemaFactory
コールバックを登録できます。
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
// create GraphQLSchema
})
GraphQlSource セクションは、Spring Boot でそれを構成する方法を説明しています。
Apollo Federation の例については、federation-jvm-spring-example [GitHub] (英語) を参照してください。
RuntimeWiringConfigurer
RuntimeWiringConfigurer
を使用して登録できます。
カスタムスカラー型。
ディレクティブ処理コード。
インターフェースおよび共用体型のデフォルトの
TypeResolver
。アプリケーションは通常アノテーション付きコントローラーを使用しますが、フィールドには
DataFetcher
が使用され、これらはRuntimeWiringConfigurer
であるAnnotatedControllerConfigurer
によってDataFetcher
として検出および登録されます。Boot スターターはAnnotatedControllerConfigurer
を自動的に登録します。
GraphQL Java、サーバーアプリケーションは、Jackson をデータのマップとの間の直列化にのみ使用します。クライアント入力はマップに解析されます。サーバー出力は、フィールド選択セットに基づいてマップにアセンブルされます。これは、Jackson シリアライゼーション / デシリアライゼーションアノテーションに依存できないことを意味します。代わりに、カスタムスカラー型 (英語) を使用できます。 |
Boot スターターは、型 RuntimeWiringConfigurer
の Bean を検出し、GraphQlSource.Builder
に登録します。つまり、ほとんどの場合、構成には次のようなものがあります。
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(BookRepository repository) {
GraphQLScalarType scalarType = ... ;
SchemaDirectiveWiring directiveWiring = ... ;
DataFetcher dataFetcher = QuerydslDataFetcher.builder(repository).single();
return wiringBuilder -> wiringBuilder
.scalar(scalarType)
.directiveWiring(directiveWiring)
.type("Query", builder -> builder.dataFetcher("book", dataFetcher));
}
}
WiringFactory
を追加する必要がある場合。スキーマ定義を考慮して登録を行うには、RuntimeWiring.Builder
と出力 List<WiringFactory>
の両方を受け入れる代替 configure
メソッドを実装します。これにより、任意の数のファクトリを追加して、順番に呼び出すことができます。
TypeResolver
GraphQlSource.Builder
は、RuntimeWiringConfigurer
を介してまだ登録されていない GraphQL インターフェースおよびユニオンに使用するデフォルトの TypeResolver
として ClassNameTypeResolver
を登録します。GraphQL Java の TypeResolver
の目的は、GraphQL インターフェースまたは Union フィールドの DataFetcher
から返される値の GraphQL オブジェクト型を決定することです。
ClassNameTypeResolver
は、値の単純なクラス名を GraphQL オブジェクト型に一致させようとします。一致しない場合は、基本クラスやインターフェースを含むスーパー型をナビゲートして、一致を探します。ClassNameTypeResolver
は、Class
から GraphQL オブジェクト型名へのマッピングとともに、名前抽出関数を構成するオプションを提供します。これは、より多くのコーナーケースをカバーできます。
GraphQlSource.Builder builder = ...
ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
classNameTypeResolver.setClassNameExtractor((klass) -> {
// Implement Custom ClassName Extractor here
});
builder.defaultTypeResolver(classNameTypeResolver);
GraphQlSource セクションは、Spring Boot でそれを構成する方法を説明しています。
ディレクティブ
GraphQL 言語は、「GraphQL ドキュメントで代替ランタイム実行と型検証動作を記述する」ディレクティブをサポートしています。ディレクティブは Java のアノテーションに似ていますが、GraphQL ドキュメントの型、フィールド、フラグメント、操作で宣言されています。
GraphQL Java は、アプリケーションがディレクティブを検出して処理するのに役立つ SchemaDirectiveWiring
契約を提供します。詳細については、GraphQL Java ドキュメントのスキーマディレクティブ (英語) を参照してください。
Spring GraphQL では、RuntimeWiringConfigurer
を介して SchemaDirectiveWiring
を登録できます。Boot スターターはそのような Bean を検出するため、次のようなものになる可能性があります。
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
}
}
ディレクティブサポートの例については、Graphql Java の拡張検証 [GitHub] (英語) ライブラリを確認してください。 |
ExecutionStrategy
GraphQL Java の ExecutionStrategy
は、リクエストされたフィールドのフェッチを実行します。ExecutionStrategy
を作成するには、DataFetcherExceptionHandler
を提供する必要があります。デフォルトでは、Spring for GraphQL は例外に従って使用する例外ハンドラーを作成し、それを GraphQL.Builder
に設定します。次に、GraphQL Java はそれを使用して、構成された例外ハンドラーを持つ AsyncExecutionStrategy
インスタンスを作成します。
カスタム ExecutionStrategy
を作成する必要がある場合は、同じ方法で DataFetcherExceptionResolver
を検出し、例外ハンドラーを作成し、それを使用してカスタム ExecutionStrategy
を作成できます。例: Spring Boot アプリケーションの場合:
@Bean
GraphQlSourceBuilderCustomizer sourceBuilderCustomizer(
ObjectProvider<DataFetcherExceptionResolver> resolvers) {
DataFetcherExceptionHandler exceptionHandler =
DataFetcherExceptionResolver.createExceptionHandler(resolvers.stream().toList());
AsyncExecutionStrategy strategy = new CustomAsyncExecutionStrategy(exceptionHandler);
return sourceBuilder -> sourceBuilder.configureGraphQl(builder ->
builder.queryExecutionStrategy(strategy).mutationExecutionStrategy(strategy));
}
スキーマ変換
スキーマの作成後にスキーマをトラバースして変換し、スキーマに変更を加えたい場合は、builder.schemaResources(..).typeVisitorsToTransformSchema(..)
を介して graphql.schema.GraphQLTypeVisitor
を登録できます。これはスキーマトラバーサルよりもコストがかかるため、スキーマを変更する必要がない限り、通常は変換よりもトラバーサルを優先することに注意してください。
スキーマトラバーサル
スキーマの作成後にスキーマをトラバースし、変更を GraphQLCodeRegistry
に適用する場合は、builder.schemaResources(..).typeVisitors(..)
を介して graphql.schema.GraphQLTypeVisitor
を登録できます。ただし、そのような訪問者はスキーマを変更できないことに注意してください。スキーマを変更する必要がある場合は、スキーマ変換を参照してください。
スキーママッピングインスペクション
クエリ、ミューテーション、サブスクリプション操作に DataFetcher
がない場合、データは返されず、役立つことは何もありません。同様に、DataFetcher
登録によって明示的にカバーされず、一致する Java オブジェクトプロパティを検索するデフォルトの PropertyDataFetcher
によって暗黙的にカバーされない操作によって返されるスキーマ型のフィールドは、常に null
になります。
GraphQL Java は、すべてのスキーマフィールドがカバーされていることを確認するチェックを実行しないため、テストカバレッジによっては検出できないギャップが生じる可能性があります。実行時に、「サイレント」 null
が返されるか、フィールドが null 値を許容しない場合はエラーが返されることがあります。下位レベルのライブラリである GraphQL Java は、DataFetcher
実装とその戻り値の型について十分に理解していないため、スキーマ型の構造と Java オブジェクトの構造を比較できません。
Spring for GraphQL は、DataFetcher
が戻り値の型情報を公開できるようにする SelfDescribingDataFetcher
インターフェースを定義します。すべての Spring DataFetcher
実装はこのインターフェースを実装します。これには、アノテーション付きコントローラーのリポジトリ、Querydsl および例示による問い合わせ Spring Data リポジトリのリポジトリが含まれます。アノテーション付きコントローラーの場合、戻り値の型は、@SchemaMapping
メソッドで宣言された戻り値の型から派生します。
起動時に、Spring for GraphQL はスキーマフィールド、DataFetcher
登録、DataFetcher
実装から返された Java オブジェクトのプロパティをインスペクションして、すべてのスキーマフィールドが明示的に登録された DataFetcher
または一致する Java オブジェクトプロパティのいずれかによってカバーされているかどうかを確認できます。インスペクションは、存在しないスキーマフィールドに対して DataFetcher
登録を探す逆方向チェックも実行します。
スキーママッピングの インスペクションを有効にするには:
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.inspectSchemaMappings(report -> {
logger.debug(report);
})
以下はレポートの例です。
GraphQL schema inspection: Unmapped fields: {Book=[title], Author[firstName, lastName]} (1) Unmapped registrations: {Book.reviews=BookController#reviews[1 args]} (2) Skipped types: [BookOrAuthor] (3)
1 | マップされていないスキーマフィールドとそのソース型のリスト |
2 | 存在しないフィールドの DataFetcher 登録のリスト |
3 | 次に説明するように、スキップされるスキーマ型のリスト |
特に Java 型情報が不十分な場合、スキーマフィールドインスペクションが実行できる内容には制限があります。これは、アノテーション付きコントローラーメソッドが java.lang.Object
を返すように宣言されている場合、戻り値の型に List<?>
などの未指定のジェネリクスパラメーターがある場合、または DataFetcher
が SelfDescribingDataFetcher
を実装しておらず、戻り値の型さえ不明な場合に当てはまります。このような場合、Java オブジェクト型の構造は不明なままとなり、結果のレポートではスキーマ型がスキップされたものとしてリストされます。スキップされた型ごとに、スキップされた理由を示す DEBUG メッセージが記録されます。
Java ではコントローラーメソッドでそのような戻り値の型を宣言する方法がなく、Java の型構造が不明であるため、スキーマ共用体型は常にスキップされます。
スキーマインターフェース型は、直接宣言されたフィールドに限りサポートされ、SelfDescribingDataFetcher
によって宣言された Java 型のプロパティと比較されます。具体的な実装の追加フィールドはインスペクションされません。これは将来のリリースで改善され、スキーマ interface
実装型もインスペクションし、宣言された Java 戻り値の型のサブ型間で一致するものを見つけようとする可能性があります。
オペレーションキャッシング
GraphQL Java は、操作を実行する前に、操作を解析して検証する必要があります。これは、パフォーマンスに大きな影響を与える可能性があります。再解析と検証の必要性を回避するために、アプリケーションは Document インスタンスをキャッシュして再利用する PreparsedDocumentProvider
を構成できます。GraphQL Java ドキュメント (英語) は、PreparsedDocumentProvider
を介したクエリキャッシュの詳細を提供します。
Spring GraphQL では、GraphQlSource.Builder#configureGraphQl
を通じて PreparsedDocumentProvider
を登録できます。
// Typically, accessed through Spring Boot's GraphQlSourceBuilderCustomizer
GraphQlSource.Builder builder = ...
// Create provider
PreparsedDocumentProvider provider =
new ApolloPersistedQuerySupport(new InMemoryPersistedQueryCache(Collections.emptyMap()));
builder.schemaResources(..)
.configureRuntimeWiring(..)
.configureGraphQl(graphQLBuilder -> graphQLBuilder.preparsedDocumentProvider(provider))
GraphQlSource セクションは、Spring Boot でそれを構成する方法を説明しています。
スレッドモデル
ほとんどの GraphQL リクエストは、ネストされたフィールドの取得における同時実行の恩恵を受けます。これが、今日のほとんどのアプリケーションが GraphQL Java の AsyncExecutionStrategy
に依存している理由です。これにより、データフェッチャーは CompletionStage
を返し、シリアルではなく同時実行が可能になります。
Java 21 と仮想スレッドは、より多くのスレッドを効率的に使用するための重要な機能を追加しますが、リクエストの実行をより迅速に完了するには、シリアルではなく同時に実行する必要があります。
Spring for GraphQL は以下をサポートします。
リアクティブデータフェッチャーであり、これらは
AsyncExecutionStrategy
によって期待されるようにCompletionStage
に適応されます。戻り値として
CompletionStage
。Kotlin コルーチンメソッドであるコントローラーメソッド。
@SchemaMapping および @BatchMapping メソッドは、Spring Framework
VirtualThreadTaskExecutor
などのExecutor
に送信されたCallable
を返すことができます。これを有効にするには、AnnotatedControllerConfigurer
でExecutor
を構成する必要があります。
Spring for GraphQL は、トランスポートとして Spring MVC または WebFlux のいずれかで実行されます。Spring MVC は、結果の CompletableFuture
が GraphQL Java エンジンが返された直後に実行されない限り、非同期リクエスト実行を使用します。これは、リクエストが十分に単純で、非同期データフェッチを必要としない場合に当てはまります。
リアクティブ DataFetcher
デフォルトの GraphQlSource
ビルダーは、DataFetcher
が Mono
または Flux
を返すためのサポートを有効にします。これは、Flux
値が集約されてリストに変換される CompletableFuture
に適応させます。ただし、リクエストが GraphQL サブスクリプションリクエストである場合を除きます。この場合、戻り値は Reactive Streams Publisher
のままです。GraphQL レスポンスのストリーミング用。
リアクティブ DataFetcher
は、トランスポート層 (WebFlux リクエスト処理など) から伝播された Reactor コンテキストへのアクセスに依存できます。WebFlux コンテキストを参照してください。
コンテキストの伝播
Spring for GraphQL は、HTTP トランスポートから GraphQL Java を介して、DataFetcher
およびそれが呼び出す他のコンポーネントにコンテキストを透過的に伝播するためのサポートを提供します。これには、Spring MVC リクエスト処理スレッドからの ThreadLocal
コンテキストと WebFlux 処理パイプラインからの Reactor Context
の両方が含まれます。
WebMvc
GraphQL Java によって呼び出される DataFetcher
およびその他のコンポーネントは、たとえば非同期 WebGraphQlInterceptor
または DataFetcher
が別のスレッドに切り替わる場合など、常に Spring MVC ハンドラーと同じスレッドで実行されるとは限りません。
Spring for GraphQL は、サーブレットコンテナースレッドから、実行するために GraphQL Java によって呼び出される DataFetcher
およびその他のコンポーネントのスレッドへの ThreadLocal
値の伝播をサポートします。これを行うには、対象の ThreadLocal
値に対してアプリケーションで io.micrometer.context.ThreadLocalAccessor
を実装する必要があります。
public class RequestAttributesAccessor implements ThreadLocalAccessor<RequestAttributes> {
@Override
public Object key() {
return RequestAttributesAccessor.class.getName();
}
@Override
public RequestAttributes getValue() {
return RequestContextHolder.getRequestAttributes();
}
@Override
public void setValue(RequestAttributes attributes) {
RequestContextHolder.setRequestAttributes(attributes);
}
@Override
public void reset() {
RequestContextHolder.resetRequestAttributes();
}
}
io.micrometer.context.ContextRegistry#getInstance()
経由でアクセスできるグローバル ContextRegistry
インスタンスを使用して、起動時に ThreadLocalAccessor
を手動で登録できます。java.util.ServiceLoader
メカニズムを介して自動的に登録することもできます。
WebFlux
リアクティブ DataFetcher
は、チェーンを処理する WebFlux リクエストから発生する Reactor コンテキストへのアクセスに依存できます。これには、WebGraphQlInterceptor コンポーネントによって追加された Reactor コンテキストが含まれます。
例外
GraphQL Java では、DataFetcherExceptionHandler
は、レスポンスの「エラー」セクションでデータ取得の例外を表現する方法を決定します。アプリケーションは単一のハンドラーのみを登録できます。
Spring for GraphQL は、デフォルトの処理を提供し、DataFetcherExceptionResolver
契約を有効にする DataFetcherExceptionHandler
を登録します。アプリケーションは GraphQLSource
ビルダーを介して任意の数のリゾルバーを登録でき、それらは Exception
を List<graphql.GraphQLError>
に解決するまで順番に登録されます。Spring Boot スターターは、この型の Bean を検出します。
DataFetcherExceptionResolverAdapter
は、protected メソッド resolveToSingleError
および resolveToMultipleErrors
を備えた便利な基本クラスです。
アノテーション付きコントローラープログラミングモデルでは、柔軟なメソッドシグネチャーを持つアノテーション付き例外ハンドラーメソッドを使用してデータフェッチ例外を処理できます。詳細については、@GraphQlExceptionHandler
を参照してください。
GraphQLError
は、以下を定義する GraphQL Java graphql.ErrorClassification
または Spring GraphQL ErrorType
に基づいてカテゴリに割り当てることができます。
BAD_REQUEST
UNAUTHORIZED
FORBIDDEN
NOT_FOUND
INTERNAL_ERROR
例外が未解決のままの場合、デフォルトでは、カテゴリ名と DataFetchingEnvironment
からの executionId
を含む一般的なメッセージを持つ INTERNAL_ERROR
として分類されます。実装の詳細が漏洩しないように、メッセージは意図的に不透明になっています。アプリケーションは、DataFetcherExceptionResolver
を使用してエラーの詳細をカスタマイズできます。
未解決の例外は、executionId
とともに ERROR レベルでログに記録され、クライアントに送信されたエラーに関連付けられます。解決された例外は DEBUG レベルで記録されます。
例外のリクエスト
GraphQL Java エンジンは、リクエストの解析時に検証またはその他のエラーが発生し、リクエストの実行が妨げられる場合があります。このような場合、レスポンスには、null
を含む「データ」キーと、グローバルな、つまりフィールドパスがない 1 つ以上のリクエストレベルの「エラー」が含まれます。
DataFetcherExceptionResolver
は、実行が開始される前、DataFetcher
が呼び出される前に発生するため、このようなグローバルエラーを処理できません。アプリケーションは、トランスポートレベルのインターセプターを使用して、ExecutionResult
のエラーをインスペクションおよび変換できます。WebGraphQlInterceptor
の例を参照してください。
サブスクリプションの例外
サブスクリプションリクエストの Publisher
は、エラーシグナルで完了する場合があります。この場合、基になるトランスポート (WebSocket など) は、GraphQL エラーのリストを含む最終的な「エラー」型のメッセージを送信します。
データ DataFetcher
は最初に Publisher
を作成するだけなので、DataFetcherExceptionResolver
はサブスクリプション Publisher
からのエラーを解決できません。その後、トランスポートは Publisher
にサブスクライブし、エラーで完了する可能性があります。
アプリケーションは、サブスクリプション Publisher
からの例外を解決して GraphQL エラーに解決し、クライアントに送信するために SubscriptionExceptionResolver
を登録できます。
ページネーション
GraphQL カーソル接続仕様 (英語) は、各アイテムがカーソルとペアになっているアイテムのサブセットを一度に返すことによって、大規模な結果セットをナビゲートする方法を定義します。クライアントは、参照されたアイテムの前後に追加のアイテムをリクエストするためにこのカーソルを使用できます。
仕様では、このパターンを「接続」と呼びます。名前が Connection で終わるスキーマ型は、ページ分割された結果セットを表す接続型です。すべての ~Connection
型には、~Edge
型が実際のアイテムとカーソルをペアにする「エッジ」フィールドと、前方および後方にアイテムがさらにあるかどうかを示すブールフラグを持つ "pageInfo" フィールドが含まれます。
接続タイプ
Connection
型定義は、ページネーションが必要な型ごとに作成する必要があり、スキーマにボイラープレートとノイズが追加されます。Spring for GraphQL は、解析されたスキーマファイルにこれらの型が存在しない場合に、起動時にこれらの型を追加する ConnectionTypeDefinitionConfigurer
を提供します。つまり、スキーマではこれだけが必要です。
Query {
books(first:Int, after:String, last:Int, before:String): BookConnection
}
type Book {
id: ID!
title: String!
}
仕様で定義されている前方ページネーション引数 first
および after
は、クライアントが指定されたカーソルの後の最初の N 項目をリクエストするために使用できるのに対し、last
および before
は、指定されたカーソルの前の最後の N 項目をリクエストするための後方ページネーション引数であることに注意してください。
次に、ConnectionTypeDefinitionConfigurer
を次のように構成します。
GraphQlSource.schemaResourceBuilder()
.schemaResources(..)
.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer)
次の型定義が透過的にスキーマに追加されます。
type BookConnection {
edges: [BookEdge]!
pageInfo: PageInfo!
}
type BookEdge {
node: Book!
cursor: String!
}
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String
endCursor: String
}
Boot スターターはデフォルトで ConnectionTypeDefinitionConfigurer
を登録します。
ConnectionAdapter
接続タイプがスキーマで使用可能になったら、同等の Java 型も必要になります。GraphQL Java は、汎用の Connection
および Edge
に加えて PageInfo
を含む提供します。
1 つのオプションは、Connection
を設定し、それをコントローラーメソッドまたは DataFetcher
から返すことです。ただし、これには、Connection
を作成し、カーソルを作成し、各項目を Edge
としてラップし、PageInfo
を作成するボイラープレートコードが必要です。さらに、Spring Data リポジトリを使用する場合など、基礎となるページネーションメカニズムがすでに存在している場合もあります。
Spring for GraphQL は、項目のコンテナーを Connection
に適合させるための ConnectionAdapter
契約を定義します。アダプターは DataFetcher
デコレーターを通じて適用され、その後、ConnectionFieldTypeVisitor
を通じてインストールされます。次のように設定できます。
ConnectionAdapter adapter = ... ;
GraphQLTypeVisitor visitor = ConnectionFieldTypeVisitor.create(List.of(adapter)) (1)
GraphQlSource.schemaResourceBuilder()
.schemaResources(..)
.typeDefinitionConfigurer(..)
.typeVisitors(List.of(visitor)) (2)
1 | 1 つ以上の ConnectionAdapter を使用して型 ビジターを作成します。 |
2 | 型ビジターを登録します。 |
Spring Data の Window
および Slice
には組み込みの ConnectionAdapter
があります。独自のカスタムアダプターを作成することもできます。ConnectionAdapter
実装は、返された項目のカーソルを作成するために CursorStrategy
に依存します。同じ戦略は、ページネーション入力を含む Subrange
コントローラーメソッド引数をサポートするためにも使用されます。
CursorStrategy
CursorStrategy
は、大きな結果セット内の項目の位置を参照する String カーソルをエンコードおよびデコードするための規約です。カーソルはインデックスまたはキーセットに基づくことができます。
ConnectionAdapter
はこれを使用して、返された項目のカーソルをエンコードします。アノテーション付きコントローラーメソッド、Querydsl リポジトリ、および例示による問い合わせリポジトリは、これを使用してページ分割リクエストからカーソルをデコードし、Subrange
を作成します。
CursorEncoder
は、文字列カーソルをさらにエンコードおよびデコードして、クライアントに対して不透明にする関連契約です。EncodingCursorStrategy
は CursorStrategy
と CursorEncoder
を組み合わせたものです。Base64CursorEncoder
、NoOpEncoder
を使用することも、独自に作成することもできます。
Spring Data ScrollPosition
には CursorStrategy
が内蔵されています。Spring Data が存在する場合、Boot スターターは CursorStrategy<ScrollPosition>
を Base64Encoder
に登録します。
ソート
GraphQL リクエストでソート情報を提供する標準的な方法はありません。ただし、ページネーションは安定した並べ替え順序に依存します。デフォルトの順序を使用することも、入力型を公開して GraphQL 引数からソートの詳細を抽出することもできます。
コントローラーメソッドの引数として、Spring Data の Sort
のサポートが組み込まれています。これが機能するには、SortStrategy
Bean が必要です。
バッチ読み込み
Book
とその Author
を指定すると、書籍用に 1 つの DataFetcher
を作成し、その作成者用に別の DataFetcher
を作成できます。これにより、作成者の有無にかかわらず本を選択できますが、本と作成者が一緒にロードされないことを意味します。これは、各本の作成者が個別にロードされるため、複数の本を照会する場合に特に効率的ではありません。これは、N+1 選択問題として知られています。
DataLoader
GraphQL Java は、関連するエンティティをバッチで読み込むための DataLoader
メカニズムを提供します。詳細は GraphQL Java ドキュメント (英語) で確認できます。以下は、その仕組みの概要です。
一意のキーを指定して、エンティティをロードできる
DataLoaderRegistry
にDataLoader
を登録します。DataFetcher
はDataLoader
にアクセスし、使用して ID でエンティティをロードできます。DataLoader
は、Future を返すことで読み込みを延期し、バッチで実行できるようにします。DataLoader
は、ロードされたエンティティのリクエストごとのキャッシュを維持し、効率をさらに向上させることができます。
BatchLoaderRegistry
GraphQL Java の完全なバッチ読み込みメカニズムでは、いくつかの BatchLoader
インターフェースの 1 つを実装し、DataLoader
としてラップして DataLoaderRegistry
の名前で登録する必要があります。
Spring GraphQL の API は少し異なります。登録の場合、ファクトリメソッドを公開する主要な BatchLoaderRegistry
と、任意の数のバッチロード関数を作成および登録するためのビルダーが 1 つだけあります。
@Configuration
public class MyConfig {
public MyConfig(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Mono<Map<Long, Author>
});
// more registrations ...
}
}
Boot スターターは、上記のように構成に挿入できる BatchLoaderRegistry
Bean を宣言するか、バッチ読み込み関数を登録するためにコントローラーなどの任意のコンポーネントに挿入できます。次に、BatchLoaderRegistry
は DefaultExecutionGraphQlService
に注入され、リクエストごとの DataLoader
登録を保証します。
デフォルトでは、DataLoader
名はターゲットエンティティのクラス名に基づいています。これにより、@SchemaMapping
メソッドはジェネリクス型で DataLoader 引数を宣言でき、名前を指定する必要はありません。ただし、名前は、必要に応じて他の DataLoaderOptions
と共に BatchLoaderRegistry
ビルダーを介してカスタマイズできます。
デフォルトの DataLoaderOptions
をグローバルに構成し、登録の開始点として使用するには、Boot の BatchLoaderRegistry
Bean をオーバーライドし、Supplier<DataLoaderOptions>
を受け入れる DefaultBatchLoaderRegistry
のコンストラクターを使用できます。
多くの場合、関連するエンティティをロードするときに、@BatchMapping コントローラーメソッドを使用できます。これは、BatchLoaderRegistry
および DataLoader
を直接使用する必要性を回避するショートカットです。
BatchLoaderRegistry
には他にも重要な利点があります。バッチ読み込み関数と @BatchMapping
メソッドから同じ GraphQLContext
へのアクセスをサポートし、それらへのコンテキストの伝播を保証します。これが、アプリケーションがそれを使用することが期待される理由です。独自の DataLoader
登録を直接実行することは可能ですが、そのような登録では上記の利点が失われます。
バッチ読み込みのテスト
BatchLoaderRegistry
に DataLoaderRegistry
で登録を実行させることから始めます。
BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
// perform registrations...
DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
batchLoaderRegistry.registerDataLoaders(dataLoaderRegistry, graphQLContext);
これで、次のように個々の DataLoader
にアクセスしてテストできます。
DataLoader<Long, Book> loader = dataLoaderRegistry.getDataLoader(Book.class.getName());
loader.load(1L);
loader.loadMany(Arrays.asList(2L, 3L));
List<Book> books = loader.dispatchAndJoin(); // actual loading
assertThat(books).hasSize(3);
assertThat(books.get(0).getName()).isEqualTo("...");
// ...