基于模式驱动构建GraphQL应用程序 release 4.1.0 即将发布
支持生成Kotlin代码(预览阶段,目前仅支持JVM平台上的Kotlin)。
一个经典示例如下:
schema {
query: Query
}
type Query {
hero(episode: Episode) : Character
human(id : String) : Human
humans: [Human]
droid(id: ID!) : Droid
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
homePlanet: String
secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
email: Email
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}
scalar Email
本次生成代码如下:
类型(type)
枚举(enum)
返回体(response)
返回体投影(response projection)
请求体(request)
操作(operations/resolver)
根据生成的代码,实现resolver接口即可。(创建默认的resolver实现的PR不被接受,所以只能一个个实现它了)
其中QueryResolver是个聚合接口,包含所有操作(每个操作对应一个服务端接口)
package io.github.dreamylost
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jsonMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLRequest
import io.github.dreamylost.api.QueryResolver
import io.github.dreamylost.model.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
/**
* Server example at https://github.com/jxnu-liguobin/springboot-examples/tree/master/graphql-complete
* (only a normal graphql server implement by java )
* @author 梦境迷离
* @version 1.0,2020/12/14
*/
class QueryResolverImpl : QueryResolver {
override fun hero(episode: EpisodeTO?): CharacterTO? {
val heroQueryRequest = HeroQueryRequest()
heroQueryRequest.setEpisode(episode)
# 包含所有字段,递归类型的递归深度为3
val characterResponseProjection = CharacterResponseProjection().`all$`(3)
val graphQLRequest = GraphQLRequest(heroQueryRequest, characterResponseProjection)
val ret = getResponse<HeroQueryResponse>(graphQLRequest)
return ret.hero()
}
override fun human(id: String?): HumanTO? {
val humanQueryRequest = HumanQueryRequest()
humanQueryRequest.setId(id)
val humanResponseProjection = HumanResponseProjection().`all$`(1)
val graphQLRequest = GraphQLRequest(humanQueryRequest, humanResponseProjection)
val ret = getResponse<HumanQueryResponse>(graphQLRequest)
return ret.human()
}
override fun humans(): List<HumanTO?>? {
val humanQueryRequest = HumansQueryRequest()
val humanResponseProjection = HumanResponseProjection().`all$`(1)
val graphQLRequest = GraphQLRequest(humanQueryRequest, humanResponseProjection)
val ret = getResponse<HumansQueryResponse>(graphQLRequest)
return ret.humans()
}
override fun droid(id: String): DroidTO? {
val productByIdQueryRequest = DroidQueryRequest()
productByIdQueryRequest.setId(id)
val droidResponseProjection = DroidResponseProjection().`all$`(1)
val graphQLRequest = GraphQLRequest(productByIdQueryRequest, droidResponseProjection)
val ret = getResponse<DroidQueryResponse>(graphQLRequest)
return ret.droid()
}
}
inline fun <reified T> getResponse(request: GraphQLRequest, url: String = "http://localhost:8080/graphql"): T {
val json = "application/json; charset=utf-8".toMediaTypeOrNull()
val body = request.toHttpJsonBody().toRequestBody(json)
val client = OkHttpClient()
val request = Request.Builder().post(body).url(url).build()
val call = client.newCall(request)
return Jackson.mapper.readValue<T>(call.execute().body!!.string())
}
object Jackson {
val mapper = jsonMapper {
addModule(kotlinModule())
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
serializationInclusion(JsonInclude.Include.NON_ABSENT)
serializationInclusion(JsonInclude.Include.NON_NULL)
}
}
gradle配置如下
import com.kobylynskyi.graphql.codegen.model.GeneratedLanguage
import io.github.kobylynskyi.graphql.codegen.gradle.GraphQLCodegenGradleTask
plugins {
id 'java'
id "org.jetbrains.kotlin.jvm" version "1.3.71"
id "io.github.kobylynskyi.graphql.codegen" version "4.0.2-SNAPSHOT"
}
group 'io.github.dreamylost'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
sourceSets {
main.kotlin.srcDirs += "$buildDir/generated-server"
}
repositories {
maven {
url 'https://repo.gradle.org/gradle/libs-releases-local'
}
mavenCentral()
mavenLocal()
jcenter()
}
dependencies {
implementation "io.github.kobylynskyi:graphql-java-codegen:4.0.2-SNAPSHOT"
implementation "javax.validation:validation-api:2.0.1.Final"
implementation("com.squareup.okhttp3:okhttp:4.2.2")
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.0'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.0'
compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.12.0'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.12.0'
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
/**
* Generate apis and models in Kotlin
*/
compileKotlin.dependsOn "graphqlCodegenKotlinService"
sourceSets.main.java.srcDir "$buildDir/generated-server"
task graphqlCodegenKotlinService(type: GraphQLCodegenGradleTask) {
graphqlSchemas.includePattern = "schema\\.graphqls"
outputDir = new File("$buildDir/generated-server")
generateClient = true
generateApis = true
generatedLanguage = GeneratedLanguage.KOTLIN
generateBuilder = false
generateImmutableModels = true
apiPackageName = "io.github.dreamylost.api"
modelPackageName = "io.github.dreamylost.model"
customAnnotationsMapping = [
"Character": ["@com.fasterxml.jackson.annotation.JsonTypeInfo(use=com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include=com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY,property = \"__typename\")",
"@com.fasterxml.jackson.annotation.JsonSubTypes(value = arrayOf(" + System.lineSeparator() +
" com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = HumanTO::class, name = \"Human\"), " + System.lineSeparator() +
" com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = DroidTO::class, name = \"Droid\")))"],
]
modelNameSuffix = "TO"
}
使用
class QueryResolverMain {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val queryResolverImpl = QueryResolverImpl()
println("r1")
val r1 = queryResolverImpl.droid("2001")
println(r1)
println("r2")
val r2 = queryResolverImpl.humans()
println(r2)
println("r3")
val r3 = queryResolverImpl.hero(EpisodeTO.EMPIRE)
println(r3)
println("r4")
val r4 = queryResolverImpl.human("1002")
println(r4)
}
}
}