アノテーション付きコントローラー

Spring for GraphQL は、@Controller コンポーネントがアノテーションを使用して、特定の GraphQL フィールドのデータをフェッチするための柔軟なメソッドシグネチャーを持つハンドラーメソッドを宣言する、アノテーションベースのプログラミングモデルを提供します。例:

@Controller
public class GreetingController {

		@QueryMapping (1)
		public String hello() { (2)
			return "Hello, world!";
		}

}
1 このメソッドをクエリ、つまり Query 型のフィールドにバインドします。
2 アノテーションで宣言されていない場合は、メソッド名からクエリを決定します。

Spring for GraphQL は RuntimeWiring.Builder を使用して、上記のハンドラーメソッドを "hello" という名前のクエリの graphql.schema.DataFetcher として登録します。

宣言

@Controller Bean を標準の Spring Bean 定義として定義できます。@Controller ステレオタイプは、クラスパス上の @Controller および @Component クラスを検出し、それらの Bean 定義を自動登録するための Spring 一般サポートと連携して、自動検出を可能にします。また、アノテーション付きクラスのステレオタイプとしても機能し、GraphQL アプリケーションでのデータ取得コンポーネントとしてのロールを示します。

AnnotatedControllerConfigurer は @Controller Bean を検出し、それらのアノテーション付きハンドラーメソッドを RuntimeWiring.Builder を介して DataFetcher として登録します。GraphQlSource.Builder に追加できる RuntimeWiringConfigurer の実装です。Boot スターターは自動的に AnnotatedControllerConfigurer を Bean として宣言し、すべての RuntimeWiringConfigurer Bean を GraphQlSource.Builder に追加します。これにより、アノテーション付き DataFetcher のサポートが有効になります。Boot スタータードキュメントの GraphQL RuntimeWiring セクションを参照してください。

@SchemaMapping

@SchemaMapping アノテーションは、ハンドラーメソッドを GraphQL スキーマのフィールドにマップし、それがそのフィールドの DataFetcher であることを宣言します。アノテーションは、親の型名とフィールド名を指定できます。

@Controller
public class BookController {

	@SchemaMapping(typeName="Book", field="author")
	public Author getAuthor(Book book) {
		// ...
	}
}

@SchemaMapping アノテーションはこれらの属性を除外することもできます。その場合、フィールド名はデフォルトでメソッド名になり、型名はデフォルトでメソッドに注入されたソース / 親オブジェクトの単純なクラス名になります。例: 以下のデフォルトは、型が "Book" で、フィールドが "author" です。

@Controller
public class BookController {

	@SchemaMapping
	public Author author(Book book) {
		// ...
	}
}

@SchemaMapping アノテーションをクラスレベルで宣言して、クラス内のすべてのハンドラーメソッドの既定の型名を指定できます。

@Controller
@SchemaMapping(typeName="Book")
public class BookController {

	// @SchemaMapping methods for fields of the "Book" type

}

@QueryMapping@MutationMapping@SubscriptionMapping は、それ自体が @SchemaMapping でアノテーションされ、typeName がそれぞれ QueryMutation、または Subscription に事前設定されているメタアノテーションです。事実上、これらはそれぞれクエリ、ミューテーション、サブスクリプション型のフィールドのショートカットアノテーションです。例:

@Controller
public class BookController {

	@QueryMapping
	public Book bookById(@Argument Long id) {
		// ...
	}

	@MutationMapping
	public Book addBook(@Argument BookInput bookInput) {
		// ...
	}

	@SubscriptionMapping
	public Flux<Book> newPublications() {
		// ...
	}
}

@SchemaMapping ハンドラーメソッドには柔軟なシグネチャーがあり、さまざまなメソッド引数と戻り値から選択できます。

メソッド引数

スキーママッピングハンドラーメソッドは、次のメソッド引数のいずれかを持つことができます。

メソッド引数 説明

@Argument

上位レベルの型付きオブジェクトにバインドされた名前付きフィールド引数にアクセスするため。

@Argument を参照してください。

@Argument Map<String, Object>

生の引数値にアクセスするため。

@Argument を参照してください。

ArgumentValue

入力引数が省略されたか null に設定されたかを示すフラグとともに、上位レベルの型付きオブジェクトにバインドされた名前付きフィールド引数にアクセスする場合。

ArgumentValue を参照してください。

@Arguments

上位レベルの型付きオブジェクトにバインドされたすべてのフィールド引数にアクセスします。

@Arguments を参照してください。

@Arguments Map<String, Object>

引数の生のマップへのアクセス用。

@ProjectedPayload Interface

For access to field arguments through a project interface.

See @ProjectedPayload Interface.

"Source"

For access to the source (i.e. parent/container) instance of the field.

See Source.

Subrange and ScrollSubrange

ページネーション引数へのアクセス用。

ページネーションスクロールSubrange を参照してください。

Sort

並べ替えの詳細へのアクセス用。

ページネーションSort を参照してください。

DataLoader

DataLoaderRegistry 内の DataLoader へのアクセス用。

DataLoader を参照してください。

@ContextValue

DataFetchingEnvironment のメイン GraphQLContext からの属性へのアクセス用。

@LocalContextValue

DataFetchingEnvironment 内のローカル GraphQLContext からの属性へのアクセス用。

GraphQLContext

DataFetchingEnvironment からのコンテキストへのアクセス用。

java.security.Principal

利用可能な場合、Spring Security コンテキストから取得されます。

@AuthenticationPrincipal

Spring Security コンテキストから Authentication#getPrincipal() へのアクセス用。

DataFetchingFieldSelectionSet

DataFetchingEnvironment を介してクエリの選択セットにアクセスするため。

Locale, Optional<Locale>

DataFetchingEnvironment から Locale へのアクセス用。

DataFetchingEnvironment

基礎となる DataFetchingEnvironment への直接アクセス用。

戻り値

スキーママッピングハンドラーメソッドは、以下を返すことができます。

  • 任意の型の解決された値。

  • 非同期値の Mono および Fluxリアクティブ DataFetcher に従って、コントローラーメソッドと任意の DataFetcher でサポートされています。

  • Kotlin コルーチンと Flow は、Mono と Flux に適合されます。

  • java.util.concurrent.Callable を使用して、値を非同期的に生成します。これを機能させるには、AnnotatedControllerConfigurer を Executor で構成する必要があります。

Java 21+ では、AnnotatedControllerConfigurer が Executor で構成されている場合、ブロッキングメソッドシグネチャーを持つコントローラーメソッドが非同期的に呼び出されます。デフォルトでは、コントローラーメソッドは、FluxMonoCompletableFuture などの非同期型を返さず、Kotlin の中断関数でもない場合はブロッキングと見なされます。AnnotatedControllerConfigurer でブロッキングコントローラーメソッド Predicate を構成すると、どのメソッドがブロッキングと見なされるかを判断できます。

Spring for GraphQL の Spring Boot スターターは、プロパティ spring.threads.virtual.enabled が設定されている場合、仮想スレッドの Executor を使用して AnnotatedControllerConfigurer を自動的に構成します。

インターフェーススキーママッピング

コントローラーメソッドがスキーマインターフェースフィールドにマップされると、デフォルトでは、インターフェースを実装するスキーマオブジェクト型ごとに 1 つずつ、複数のマッピングに置き換えられます。これにより、すべてのサブ型に対して 1 つのコントローラーメソッドを使用できるようになります。

例: 与えられた:

type Query {
	activities: [Activity!]!
}

interface Activity {
	id: ID!
	coordinator: User!
}

type FooActivity implements Activity {
	id: ID!
	coordinator: User!
}

type BarActivity implements Activity {
	id: ID!
	coordinator: User!
}

type User {
	name: String!
}

コントローラーは次のように記述できます。

@Controller
public class BookController {

	@QueryMapping
	public List<Activity> activities() {
		// ...
	}

	@SchemaMapping
	public User coordinator(Activity activity) {
		// Called for any Activity subtype
	}

}

必要に応じて、個々のサブ型のマッピングを引き継ぐことができます。

@Controller
public class BookController {

	@QueryMapping
	public List<Activity> activities() {
		// ...
	}

	@SchemaMapping
	public User coordinator(Activity activity) {
		// Called for any Activity subtype except FooActivity
	}

	@SchemaMapping
	public User coordinator(FooActivity activity) {
		// ...
	}

}

@Argument

GraphQL Java では、DataFetchingEnvironment はフィールド固有の引数値のマップへのアクセスを提供します。値は、単純なスカラー値 (例: String、Long)、より複雑な入力用の値の Map、または値の List にすることができます。

@Argument アノテーションを使用して、引数をターゲットオブジェクトにバインドし、ハンドラーメソッドに注入します。バインドは、引数値を予想されるメソッドパラメーターの型のプライマリデータコンストラクターにマップするか、既定のコンストラクターを使用してオブジェクトを作成し、引数値をそのプロパティにマップすることによって実行されます。これが再帰的に繰り返され、ネストされたすべての引数値が使用され、それに応じてネストされたターゲットオブジェクトが作成されます。例:

@Controller
public class BookController {

	@QueryMapping
	public Book bookById(@Argument Long id) {
		// ...
	}

	@MutationMapping
	public Book addBook(@Argument BookInput bookInput) {
		// ...
	}
}
ターゲットオブジェクトに setter がなく、それを変更できない場合は、AnnotatedControllerConfigurer のプロパティを使用して、フィールドへの直接アクセスによるバインディングのフォールバックを許可できます。

デフォルトでは、メソッドパラメーター名が使用可能な場合 (Java 8+ を指定した -parameters コンパイラーフラグまたはコンパイラーからのデバッグ情報が必要)、引数の検索に使用されます。必要に応じて、アノテーションを使用して名前をカスタマイズできます。@Argument("bookInput")

@Argument アノテーションには、「必須」フラグも、デフォルト値を指定するオプションもありません。これらはどちらも GraphQL スキーマレベルで指定でき、GraphQL Java によって適用されます。

バインディングが失敗した場合、フィールドエラーとして蓄積されたバインディングの課題とともに BindException が発生します。各エラーの field は、問題が発生した引数パスです。

@Argument を Map<String, Object> 引数とともに使用すると、引数の生の値を取得できます。例:

@Controller
public class BookController {

	@MutationMapping
	public Book addBook(@Argument Map<String, Object> bookInput) {
		// ...
	}
}
1.2 より前は、アノテーションで名前が指定されていない場合、@Argument Map<String, Object> は完全な引数マップを返しました。1.2 の後、@Argument と Map<String, Object> は常に、アノテーションで指定された名前またはパラメーター名のいずれかに一致する生の引数値を返します。完全な引数マップにアクセスするには、代わりに @Arguments を使用してください。

ArgumentValue

デフォルトでは、GraphQL の入力引数は null 可能でオプションです。つまり、引数を null リテラルに設定することも、まったく指定しないこともできます。この区別は、基礎となるデータも null に設定されているか、それに応じてまったく変更されていない可能性があるミューテーションによる部分的な更新に役立ちます。@Argument を使用する場合、どちらの場合も null または空の Optional を取得するため、そのような区別を行う方法はありません。

値がまったく提供されていないかどうかを知りたくない場合は、入力引数が完全に省略されたかどうかを示すフラグとともに、結果の値の単純なコンテナーである ArgumentValue メソッドパラメーターを宣言できます。これを @Argument の代わりに使用できます。この場合、引数名はメソッドのパラメーター名から決定されます。または、@Argument と一緒に使用して引数名を指定することもできます。

例:

@Controller
public class BookController {

	@MutationMapping
	public void addBook(ArgumentValue<BookInput> bookInput) {
		if (!bookInput.isOmitted()) {
			BookInput value = bookInput.value();
			// ...
		}
	}
}

ArgumentValue は、コンストラクター引数または setter を介して初期化された @Argument メソッドパラメーターのオブジェクト構造内のフィールドとしてもサポートされます。これには、最上位オブジェクトの任意のレベルでネストされたオブジェクトのフィールドが含まれます。

@Arguments

特定の名前付き引数をバインドする @Argument とは対照的に、完全な引数マップを単一のターゲットオブジェクトにバインドする場合は、@Arguments アノテーションを使用します。

例: @Argument BookInput bookInput は引数 "bookInput" の値を使用して BookInput を初期化しますが、@Arguments は完全な引数マップを使用し、その場合、最上位の引数は BookInput プロパティにバインドされます。

@Arguments を Map<String, Object> 引数とともに使用して、すべての引数値の生のマップを取得できます。

@ProjectedPayload インターフェース

@Argument で完全なオブジェクトを使用する代わりに、射影 インターフェースを使用して、適切に定義された最小限のインターフェースを介して GraphQL リクエスト引数にアクセスすることもできます。Spring Data がクラスパス上にある場合、引数射影は Spring Data のインターフェース 射影によって提供されます。

これを利用するには、@ProjectedPayload でアノテーションを付けたインターフェースを作成し、それをコントローラーメソッドのパラメーターとして宣言します。パラメーターに @Argument のアノテーションが付けられている場合、DataFetchingEnvironment.getArguments() マップ内の個々の引数に適用されます。@Argument なしで宣言すると、射影は完全な引数マップのトップレベルの引数で機能します。

例:

@Controller
public class BookController {

	@QueryMapping
	public Book bookById(BookIdProjection bookId) {
		// ...
	}

	@MutationMapping
	public Book addBook(@Argument BookInputProjection bookInput) {
		// ...
	}
}

@ProjectedPayload
interface BookIdProjection {

	Long getId();
}

@ProjectedPayload
interface BookInputProjection {

	String getName();

	@Value("#{target.author + ' ' + target.name}")
	String getAuthorAndName();
}

ソース

GraphQL Java では、DataFetchingEnvironment はフィールドのソース (つまり、親 / コンテナー) インスタンスへのアクセスを提供します。これにアクセスするには、予想されるターゲット型のメソッドパラメーターを宣言するだけです。

@Controller
public class BookController {

	@SchemaMapping
	public Author author(Book book) {
		// ...
	}
}

ソースメソッドの引数は、マッピングの型名を決定するのにも役立ちます。Java クラスの単純な名前が GraphQL 型と一致する場合、@SchemaMapping アノテーションで型名を明示的に指定する必要はありません。

@BatchMapping ハンドラーメソッドは、ソース / 親書籍オブジェクトのリストを指定して、クエリのすべての作成者をバッチで読み込むことができます。

Subrange

Spring 構成に CursorStrategy Bean がある場合、コントローラーメソッドは Subrange<P> 引数をサポートします。<P> はカーソルから変換された相対位置です。Spring Data の場合、ScrollSubrange は ScrollPosition を公開します。例:

@Controller
public class BookController {

	@QueryMapping
	public Window<Book> books(ScrollSubrange subrange) {
		ScrollPosition position = subrange.position().orElse(ScrollPosition.offset());
		int count = subrange.count().orElse(20);
		// ...
	}

}

ページネーションと組み込みメカニズムの概要については、ページネーションを参照してください。

Sort

Spring 構成に SortStrategy Bean がある場合、コントローラーメソッドはメソッド引数として Sort をサポートします。例:

@Controller
public class BookController {

	@QueryMapping
	public Window<Book> books(Optional<Sort> optionalSort) {
		Sort sort = optionalSort.orElse(Sort.by(..));
	}

}

DataLoader

バッチ読み込みに従って、エンティティのバッチ読み込み関数を登録すると、型 DataLoader のメソッド引数を宣言することでエンティティの DataLoader にアクセスし、それを使用してエンティティを読み込むことができます。

@Controller
public class BookController {

	public BookController(BatchLoaderRegistry registry) {
		registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
			// return Map<Long, Author>
		});
	}

	@SchemaMapping
	public CompletableFuture<Author> author(Book book, DataLoader<Long, Author> loader) {
		return loader.load(book.getAuthorId());
	}

}

デフォルトでは、BatchLoaderRegistry は値型の完全なクラス名 (例: Author のクラス名) を登録のキーに使用するため、ジェネリクス型で DataLoader メソッド引数を宣言するだけで、DataLoaderRegistry でそれを見つけるのに十分な情報が提供されます。フォールバックとして、DataLoader メソッド引数リゾルバーもメソッド引数名をキーとして試行しますが、通常は必要ありません。

@SchemaMapping が単純に DataLoader に委譲する関連エンティティをロードする多くの場合、次のセクションで説明するように @BatchMapping メソッドを使用してボイラープレートを減らすことができることに注意してください。

検証

javax.validation.Validator Bean が見つかると、AnnotatedControllerConfigurer は、アノテーション付きコントローラーメソッドでの Bean バリデーションのサポートを有効にします。通常、Bean の型は LocalValidatorFactoryBean です。

Bean 検証では、型に対する制約を宣言できます。

public class BookInput {

	@NotNull
	private String title;

	@NotNull
	@Size(max=13)
	private String isbn;
}

次に、コントローラーメソッドパラメーターに @Valid のアノテーションを付けて、メソッド呼び出しの前に検証できます。

@Controller
public class BookController {

	@MutationMapping
	public Book addBook(@Argument @Valid BookInput bookInput) {
		// ...
	}
}

検証中にエラーが発生すると、ConstraintViolationException が発生します。例外チェーン を使用して、GraphQL レスポンスに含めるエラーに変換することで、それをクライアントに提示する方法を決定できます。

@Valid に加えて、検証グループを指定できる Spring の @Validated も使用できます。

Bean 検証は、@Argument@Arguments@ProjectedPayload メソッドパラメーターに役立ちますが、より一般的にはすべてのメソッドパラメーターに適用されます。

検証と Kotlin コルーチン

Hibernate Validator は Kotlin コルーチンメソッドと互換性がなく、メソッドパラメーターをイントロスペクトするときに失敗します。関連する課題と推奨される回避策へのリンクについては、spring-projects/spring-graphql#344 (コメント) [GitHub] (英語) を参照してください。

@BatchMapping

バッチ読み込みは、org.dataloader.DataLoader を使用して個々のエンティティインスタンスのロードを延期することで N+1 選択の問題に対処し、一緒にロードできるようにします。例:

@Controller
public class BookController {

	public BookController(BatchLoaderRegistry registry) {
		registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
			// return Map<Long, Author>
		});
	}

	@SchemaMapping
	public CompletableFuture<Author> author(Book book, DataLoader<Long, Author> loader) {
		return loader.load(book.getAuthorId());
	}

}

上記の関連付けられたエンティティをロードする単純なケースでは、@SchemaMapping メソッドは DataLoader に委譲するだけです。これは、@BatchMapping メソッドで回避できるボイラープレートです。例:

@Controller
public class BookController {

	@BatchMapping
	public Mono<Map<Book, Author>> author(List<Book> books) {
		// ...
	}
}

上記は、キーが Book インスタンスであり、読み込まれた値が作成者である BatchLoaderRegistry のバッチ読み込み関数になります。さらに、DataFetcher は、型 Book の author フィールドにも透過的にバインドされます。これは、そのソース / 親 Book インスタンスが与えられると、作成者の DataLoader に単純に委譲します。

一意のキーとして使用するには、Book は hashcode および equals を実装する必要があります。

デフォルトでは、フィールド名はメソッド名にデフォルト設定され、型名は入力 List 要素型の単純なクラス名にデフォルト設定されます。どちらもアノテーション属性を介してカスタマイズできます。型名は、クラスレベル @SchemaMapping から継承することもできます。

メソッド引数

バッチマッピングメソッドは、次の引数をサポートしています。

メソッド引数 説明

List<K>

ソース / 親オブジェクト。

java.security.Principal

利用可能な場合、Spring Security コンテキストから取得されます。

@ContextValue

DataFetchingEnvironment からのものと同じコンテキストである BatchLoaderEnvironment の GraphQLContext からの値へのアクセス用。

GraphQLContext

DataFetchingEnvironment からのコンテキストと同じコンテキストである BatchLoaderEnvironment からのコンテキストへのアクセス用。

BatchLoaderEnvironment

org.dataloader.BatchLoaderWithContext に対して GraphQL Java で使用できる環境。

戻り値

バッチマッピングメソッドは以下を返すことができます。

戻りの型 説明

Mono<Map<K,V>>

親オブジェクトをキーとして、バッチロードされたオブジェクトを値として持つマップ。

Flux<V>

メソッドに渡されたソース / 親オブジェクトと同じ順序である必要があるバッチロードオブジェクトのシーケンス。

Map<K,V>, Collection<V>

命令型のバリアント。リモート 呼び出しを行う必要はありません。

Callable<Map<K,V>>, Callable<Collection<V>>

非同期で呼び出される命令型バリアント。これを機能させるには、AnnotatedControllerConfigurer を Executor で構成する必要があります。

Map<K,V>、Kotlin、Flow<K,V> を使用した Kotlin コルーチン

Mono<Map<K,V> および Flux<V> に適合します。

Java 21+ では、AnnotatedControllerConfigurer が Executor で構成されている場合、ブロッキングメソッドシグネチャーを持つコントローラーメソッドが非同期的に呼び出されます。デフォルトでは、コントローラーメソッドは、FluxMonoCompletableFuture などの非同期型を返さず、Kotlin の中断関数でもない場合はブロッキングと見なされます。AnnotatedControllerConfigurer でブロッキングコントローラーメソッド Predicate を構成すると、どのメソッドがブロッキングと見なされるかを判断できます。

Spring for GraphQL の Spring Boot スターターは、プロパティ spring.threads.virtual.enabled が設定されている場合、仮想スレッドの Executor を使用して AnnotatedControllerConfigurer を自動的に構成します。

インターフェースバッチマッピング

インターフェーススキーママッピングの場合と同様に、バッチマッピングメソッドがスキーマインターフェースフィールドにマップされると、マッピングは、インターフェースを実装するスキーマオブジェクト型ごとに 1 つずつ、複数のマッピングに置き換えられます。

つまり、次のようになります。

type Query {
	activities: [Activity!]!
}

interface Activity {
	id: ID!
	coordinator: User!
}

type FooActivity implements Activity {
	id: ID!
	coordinator: User!
}

type BarActivity implements Activity {
	id: ID!
	coordinator: User!
}

type User {
	name: String!
}

コントローラーは次のように記述できます。

@Controller
public class BookController {

	@QueryMapping
	public List<Activity> activities() {
		// ...
	}

	@BatchMapping
	Map<Activity, User> coordinator(List<Activity> activities) {
		// Called for all Activity subtypes
	}
}

必要に応じて、個々のサブ型のマッピングを引き継ぐことができます。

@Controller
public class BookController {

	@QueryMapping
	public List<Activity> activities() {
		// ...
	}

	@BatchMapping
	Map<Activity, User> coordinator(List<Activity> activities) {
		// Called for all Activity subtypes
	}

	@BatchMapping(field = "coordinator")
	Map<Activity, User> fooCoordinator(List<FooActivity> activities) {
		// ...
	}
}

@GraphQlExceptionHandler

@GraphQlExceptionHandler メソッドを使用して、柔軟なメソッドシグネチャーによるデータフェッチからの例外を処理します。コントローラー内で宣言された場合、例外ハンドラーメソッドは同じコントローラーからの例外に適用されます。

@Controller
public class BookController {

	@QueryMapping
	public Book bookById(@Argument Long id) {
		// ...
	}

	@GraphQlExceptionHandler
	public GraphQLError handle(BindException ex) {
		return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
	}
}

@ControllerAdvice で宣言すると、例外ハンドラーメソッドはコントローラー全体に適用されます。

@ControllerAdvice
public class GlobalExceptionHandler {

	@GraphQlExceptionHandler
	public GraphQLError handle(BindException ex) {
		return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
	}

}

@GraphQlExceptionHandler メソッドによる例外処理は、コントローラーの呼び出しに自動的に適用されます。コントローラーメソッドに基づいていない他の graphql.schema.DataFetcher 実装からの例外を処理するには、AnnotatedControllerConfigurer から DataFetcherExceptionResolver を取得し、それを DataFetcherExceptionResolver として GraphQlSource.Builder に登録します。

メソッド署名

例外ハンドラーメソッドは、DataFetchingEnvironment, から解決され、@SchemaMapping メソッドのメソッド引数と一致するメソッド引数を使用した柔軟なメソッドシグネチャーをサポートします。

サポートされている戻り値の型を以下に示します。

戻りの型 説明

graphql.GraphQLError

単一フィールドのエラーに対する例外を解決します。

Collection<GraphQLError>

複数フィールドエラーの例外を解決します。

void

レスポンスエラーを発生させずに例外を解決します。

Object

例外を 1 つのエラーに解決するか、複数のエラーに解決するか、何も解決しません。戻り値は GraphQLErrorCollection<GraphQLError>、または null である必要があります。

Mono<T>

非同期解決の場合、<T> はサポートされている同期戻り値の型の 1 つです。

名前空間

スキーマレベルでは、クエリと変更操作は、Query 型と Mutation 型に直接定義されます。リッチ GraphQL API は、これらの型に数十の操作を定義できるため、API を調べて関心事を分離することが難しくなります。GraphQL スキーマで名前空間を定義する (英語) ことを選択できます。このアプローチにはいくつか注意点がありますが、Spring for GraphQL アノテーション付きコントローラーを使用してこのパターンを実装できます。

名前空間を使用すると、GraphQL スキーマは、たとえば、クエリ操作を Query に直接リストするのではなく、トップレベルの型にネストすることができます。ここでは、MusicQueries および UserQueries 型を定義し、Query で使用できるようにします。

type Query {
    music: MusicQueries
    users: UserQueries
}

type MusicQueries {
    album(id: ID!): Album
    searchForArtist(name: String!): [Artist]
}

type Album {
    id: ID!
    title: String!
}

type Artist {
    id: ID!
    name: String!
}

type UserQueries {
    user(login: String): User
}

type User {
    id: ID!
    login: String!
}

GraphQL クライアントは次のように album クエリを使用します。

{
  music {
    album(id: 42) {
      id
      title
    }
  }
}

そして、次のレスポンスが得られます。

{
  "data": {
    "music": {
      "album": {
        "id": "42",
        "title": "Spring for GraphQL"
      }
    }
  }
}

これは、次のパターンを使用して @Controller に実装できます。

import java.util.List;

import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;

@Controller
@SchemaMapping(typeName = "MusicQueries") (1)
public class MusicController {

	@QueryMapping (2)
	public MusicQueries music() {
		return new MusicQueries();
	}

	(3)
	public record MusicQueries() {

	}

	@SchemaMapping (4)
	public Album album(@Argument String id) {
		return new Album(id, "Spring GraphQL");
	}

	@SchemaMapping
	public List<Artist> searchForArtist(@Argument String name) {
		return List.of(new Artist("100", "the Spring team"));
	}


}
1 メソッドで繰り返し使用されないように、コントローラーに @SchemaMapping と typeName 属性をアノテーションします。
2「音楽」名前空間の @QueryMapping を定義する
3「音楽」クエリは「空」のレコードを返しますが、空のマップを返すこともできます。
4 クエリは "MusicQueries" 型のフィールドとして宣言されるようになりました

コントローラーで折り返し型 ("MusicQueries" ,"UserQueries" ) を明示的に宣言する代わりに、GraphQlSourceBuilderCustomizer と Spring Boot を使用してランタイム接続で構成することもできます。

import java.util.Collections;
import java.util.List;

import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class NamespaceConfiguration {

	@Bean
	public GraphQlSourceBuilderCustomizer customizer() {
		List<String> queryWrappers = List.of("music", "users"); (1)

		return (sourceBuilder) -> sourceBuilder.configureRuntimeWiring((wiringBuilder) ->
				queryWrappers.forEach((field) -> wiringBuilder.type("Query",
						(builder) -> builder.dataFetcher(field, (env) -> Collections.emptyMap()))) (2)
		);
	}

}
1「クエリ」型のすべてのラッパー型を一覧表示します
2 それぞれに対して手動でデータフェッチャーを宣言し、空のマップを返す