Spring for GraphQLでもスキーマと実装に不一致があればSpring Bootの起動を停止したい

既に1年近く前の話ですが、愛用していた[GraphQL Spring Boot Starters](https://github.com/graphql-java-kickstart/graphql-spring-boot)がアーカイブになってしまいました。Spring for GraphQLの登場で予想はしていましたが、まだ機能面で不満があったので残念です。

しかし、時代は待ってくれませんし、Spring for GraphQLでのDataLoaderの実装(特に@BatchMapping)は簡単になりますし、Spring Frameworkなので今後の進歩も期待できるので、移行は大歓迎でもあります。


## 不満

不満とは、スキーマと実装に不一致があったときの挙動で

* GraphQL Spring Boot Starters
```java
2024-10-12T12:34:02.149+09:00 ERROR 224838 --- [template-project] [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Unable to start web server

...中略...

Caused by: graphql.kickstart.tools.resolver.FieldResolverError: No method or field found as defined in schema <unknown>:14 with any of the following signatures (with or without one of [interface graphql.schema.DataFetchingEnvironment, class graphql.GraphQLContext] as the last argument), in priority order:

  example.domain.AccountModel.address()
  example.domain.AccountModel.getAddress()
  example.domain.AccountModel.address
```

* Spring for GraphQL
```java
2024-10-12T12:29:03.484+09:00  INFO 220161 --- [template-project] [           main] o.s.b.a.g.GraphQlAutoConfiguration       : GraphQL schema inspection:
	Unmapped fields: {AccountModel=[address]}
	Unmapped registrations: {}
	Unmapped arguments: {}
	Skipped types: []
```

GraphQL Spring Boot Startersはエラーが発生してSpring Bootの起動が停止するのに対し、Spring for GraphQLはログに出るものの、そのままSpring Bootは起動してしまいます。そして、当然ながら実行中には実装の不一致に伴うエラーが発生するわけです。

このSpring for GraphQLの挙動は大規模開発とかでは便利なんですかね?巨大なスキーマがあって、複数のアプリケーションで実装を分担するみたいな。

でも自分だとほぼ1人で完結する程度の開発なので、スキーマの一部をワザと実装しないということもないし、実装漏れには確実に気づきたいわけです。


## 解決策

まずは結論の実装コードから。

```kotlin
@Configuration
class GraphQlConfig {
  @Bean
  @ConditionalOnProperty(prefix = "spring.graphql.schema.inspection", name = ["stop-on-unmapped"], havingValue = "true", matchIfMissing = false)
  fun graphQlSourceBuilderReportConsumerCustomizer(): GraphQlSourceBuilderCustomizer {
    return GraphQlSourceBuilderCustomizer{ builder: GraphQlSource.SchemaResourceBuilder ->
      builder.inspectSchemaMappings { report ->
        check(report.unmappedArguments().isEmpty()
          && report.unmappedFields().isEmpty()
          && report.unmappedRegistrations().isEmpty()
          && report.skippedTypes().isEmpty()) {
          report.toString()
        }
      }
    }
  }
}
```

Spring for GraphQLの自動設定だと、GraphQlAutoConfiguration#graphQlSourceの中で、GraphQlSource.SchemaResourceBuilder#inspectSchemaMappingsメソッドにログオブジェクトのinfoメソッドを渡しています。これによりGraphQlSource.SchemaResourceBuilder#initGraphQlSchemaの中でSchemaReportが生成され、ログとして出力されていました。

そこで自作のGraphQlSourceBuilderCustomizerにより、
GraphQlSource.SchemaResourceBuilder#inspectSchemaMappingsメソッドに渡すオブジェクトを上書きし、SchemaReportで不一致を検出したら例外を発生させるようにしました。

また、この挙動をプロパティで制御できるようにもしました。


これにより不一致がある場合は以下のような出力でSpring Bootの起動が停止するようになりました。

```java
2024-10-12T13:30:59.211+09:00 ERROR 255549 --- [template-project] [           main] o.s.boot.SpringApplication               : Application run failed

...中略...

Caused by: java.lang.IllegalStateException: GraphQL schema inspection:
	Unmapped fields: {AccountModel=[address]}
	Unmapped registrations: {}
	Unmapped arguments: {}
	Skipped types: []
```

## まとめ
この他にも、Spring for GraphQLへ移行するにあたって、セキュリティやエラー処理など、いろいろと検証していきます。