我想在Webflux应用程序中使用GraphQL
在使用REST进行实现过程中,
-
- PUTやDELETEが通信経路のどこかで遮断されるから、X-HTTP-Method-Overrideにメソッドを詰めてPOSTせざるを得ず、面倒。
- リソース毎に通信が必要だから、複数リソースを跨ったトランザクションってできないよね。どうしよう。
在我被各种烦恼所困扰的时候,我了解到了GraphQL的存在。
所以,参考了《用Spring Boot尝试GraphQL》等资料后,我对GraphQL有了相当不错的感觉。
- でも、必要なフィールドを絞れるのは良いけど、絞りたくないときに*のようなワイルドカードが指定できないのが玉に瑕だと思った。
所以我正在使用Webflux开发的应用程序中,研究是否可以提供GraphQL的API。在查阅上述文章时,我发现GraphQL Java在去年12月发布了graphql-java-spring-boot-starter-webflux,于是我进行了进一步的调查。
GraphQL-Java-Spring-Boot-Starter-WebFlux 的中文翻译为 “GraphQL-Java-Spring-Boot-Starter-WebFlux”。
当我在Maven Repository中进行搜索时,我发现除了最初的1.0版本之外,其余版本都是按照日期顺序排列的,我犹豫不决不知道该选择哪个版本。然而,由于我喜欢尝试新事物,所以我选择了当前最新的版本。
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-spring-boot-starter-webflux</artifactId>
<version>2019-06-24T11-47-27-31ab4f9</version>
</dependency>
查看源码后发现,graphql-java-spring-boot-starter-webflux只是将依赖的graphql-java-spring-webflux中的GraphQLController类注册为Bean。
然而,没有相应的实现可以方便地验证GraphQL通信,就像graphiql-spring-boot-starter一样。
GraphQL-Java-Spring-WebFlux
我主要研究了GraphQLController。
-
- GraphQLControllerクラスは名前から察せられるように@RestControllerが付与されたコントローラ。なので聞いた噂によると、WebfluxをFunctional Endpointモデルで開発していると使えないと思われる。
-
- 自分的にはAnnotated Controllerモデルの方が可読性的に気に入っているので、今のところ問題ない。
-
- GraphQLControllerが受け付けるパスは、graphql.urlプロパティで指定できる。デフォルトはgrapshql。
-
- POSTリクエストのContent-TypeはJSON(application/json)とGraphQL(application/graphql)に対応。なお、単純な文字列一致で判定しているので、application/json;charset=UTF-8だと処理してくれない。
- 依存関係をみるとgraphql-java-toolsは含まれておらず、こちらでGraphQLオブジェクトをBean登録しておく必要がある。
示例
在进行调查的同时,我使用Kotlin对样本进行了实现。
@SpringBootApplication
class GraphqlWebfluxApplication(val userRepo: UserRepository) : CommandLineRunner {
override fun run(vararg args: String?) {
userRepo.saveAll(listOf(User(name = "ユウキ"), User(name = "カーシャ"), User(name = "ギジェ")))
}
@Bean
fun graphql(appContext: ApplicationContext, resolvers: List<GraphQLResolver<*>>): GraphQL {
val builder = SchemaParserBuilder()
val schemas: List<String> = appContext
.getResources("classpath*:/graphql/*.graphqls")
.map { StreamUtils.copyToString(it.inputStream, StandardCharsets.UTF_8) }
.onEach { builder.schemaString(it) }
.toList()
val schemaParser = builder.resolvers(resolvers).build();
val graphQLSchema = schemaParser.makeExecutableSchema();
return GraphQL.newGraphQL(graphQLSchema).build()
}
}
fun main(args: Array<String>) {
runApplication<GraphqlWebfluxApplication>(*args)
}
@Entity
data class User(
@field:Id
@field:GeneratedValue
var id: Int = -1,
var name: String = StringUtils.EMPTY)
interface UserRepository : JpaRepository<User, Int>
@Component
class QueryResolver(val userRepo: UserRepository) : GraphQLQueryResolver {
fun users(): List<User> {
return userRepo.findAll()
}
}
@Component
class MutationResolver(val userRepo: UserRepository) : GraphQLMutationResolver {
fun createUser(name: String): Boolean {
try {
userRepo.save(User(name = name))
return true
} catch (e: Exception) {
return false
}
}
}
生成GraphQL对象时,我们在pom.xml中添加了graphql-java-tools依赖,参考了graphql-spring-boot-starter的源代码来进行实现。
又编写了测试代码并进行了操作确认。
@RunWith(SpringRunner::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GraphqlWebfluxApplicationTests {
@Autowired
lateinit var context: ApplicationContext
@Test
fun query() {
val client = WebTestClient.bindToApplicationContext(context).build()
client
.post()
.uri("/api/v1")
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject("""
{
"query": "{ users { id name } }"
}
""".trimIndent()))
.exchange()
.expectStatus().isOk
.expectBody()
.jsonPath("$.errors").doesNotExist()
.returnResult().toString().let {
println(it)
}
}
@Test
fun mutation() {
val client = WebTestClient.bindToApplicationContext(context).build()
client
.post()
.uri("/api/v1")
.contentType(MediaType.parseMediaType("application/graphql"))
.body(BodyInserters.fromObject("""
mutation {
m1: createUser(name: "ベス")
m2: createUser(name: "カララ")
}
""".trimIndent()))
.exchange()
.expectStatus().isOk
.expectBody()
.jsonPath("$.errors").doesNotExist()
.returnResult().toString().let {
println(it)
}
query()
}
}
当执行
{
"query": "{ users { id name } }"
}
对于这个要求
{"data":{"users":[{"id":"1","name":"ユウキ"},{"id":"2","name":"カーシャ"},{"id":"3","name":"ギジェ"}]}}
可以确认对方的回复。
即使在服务器端进行GraphQL语法检查时出现错误,由于HTTP状态码为200,我们通过检查响应体中是否存在errors来确认正常性。
印象
感觉不错,尤其是。
mutation {
m1: createUser(name: "ベス")
m2: createUser(name: "カララ")
}
确认能够在一次通信中完成两个处理的操作,感到有些感动。
然而,现在需要确认事务的范围如何,需要进一步确认。
这次所创建的代码在这里。
补充(2019年7月8日)
graphql-java-tools有两个groupId,一个是com.graphql-java,另一个是com.graphql-java-kickstart,由此导致了混淆。然而,在这篇文章中,我们确认了由com.graphql-java转移到com.graphql-java-kickstart的情况。
因此,我已经将示例代码更新到com.graphql-java-kickstart的最新版本(5.6.1)。
然而,这样做会导致java.lang.NoClassDefFoundError: Could not initialize class kotlinx.coroutines.Dispatchers错误。由于graphql-java-tools 5.4.0已经添加了对协程的支持,我也将kotlin版本升级到了1.3.41。
(2020/2/1) 补充
虽然已经过了一段时间,但我认为在使用Spring Boot开发GraphQL时,我认为graphql-spring-boot的版本6.0.0是标准的,它添加了用于Webflux的模块。
然而,由于Webflux无法使用GraphiQL或Voyager等工具,所以如果性能要求不高、开发效率优先的话,使用Servlet版本的开发比较稳妥,所以我暂时放弃了使用Webflux进行开发。
但如果有机会的话,我想在graphql-spring-boot中进行Webflux开发。