spring kotlin
问题 (Problem)
Error handling in GraphQL can be pretty tricky, as a response will always have HTTP status 200 OK
. If a request fails, the JSON payload response will contain a root field called errors
that contains the details of the failure.
GraphQL中的错误处理可能非常棘手,因为响应始终具有HTTP状态200 OK
。 如果请求失败,则JSON有效负载响应将包含一个名为errors
的根字段,其中包含失败的详细信息。
For a query like the one below …
对于以下查询...
query{
getCar(input: "B Class"){
name
}
}
… without proper error handling, the response will be something like this:
……如果没有适当的错误处理,响应将是这样的:
{
"errors": [
{
"message": "Internal Server Error(s) while executing query"
}
],
"data": null
}
This isn’t helpful at all — the response doesn’t tell you what actually went wrong.
这根本没有帮助-响应不会告诉您实际出了什么问题。
解 (Solution)
1.设置 (1. The setup)
Let’s set up a Spring Boot project with GraphQL. Also, the code to this is available here.
让我们用GraphQL设置一个Spring Boot项目。 此外, 此处的代码也可用。
Go to https://start.spring.io/.
Select a Gradle
project, and set the language as Kotlin
.
选择一个Gradle
项目,并将语言设置为Kotlin
。
You don’t have to select any dependencies for a bare-minimum implementation.
您不必为最低限度的实现选择任何依赖项。
Download the ZIP file, and open it in your favorite IDE.
下载ZIP文件,然后在您喜欢的IDE中打开它。
Jump to build.gradle.kts
, and add:
跳转到build.gradle.kts
,并添加:
implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:6.0.1")
implementation("com.graphql-java-kickstart:graphql-java-tools:5.7.1")
runtimeOnly("com.graphql-java-kickstart:altair-spring-boot-starter:6.0.1")
runtimeOnly("com.graphql-java-kickstart:graphiql-spring-boot-starter:6.0.1")
runtimeOnly("com.graphql-java-kickstart:voyager-spring-boot-starter:6.0.1")
runtimeOnly("com.graphql-java-kickstart:playground-spring-boot-starter:6.0.1")
These are the GraphQL libraries you’d need to get started with your first endpoint.
这些是您的第一个端点开始需要的GraphQL库。
2.没有错误处理的端点 (2. Endpoint without error handling)
Let’s create two endpoints to create and fetch a resource.
让我们创建两个端点来创建和获取资源。
type Query{
hello: String
getCar(input: String) : Car!
}
type Mutation{
createCar(input: CarInput): Car
}
type Car {
name: String
price: Int
engineType: String
}
input CarInput{
name: String!
price: Int!
engineType: String!
}
Add Service
to provide the creation and fetching of car
.
添加Service
以提供car
的创建和获取。
@Service
class CarService {
fun getCar(input: String): Car {
return CarMap.cars[input]!!
}
fun createCar(input: CarInput): Car {
val car = Car(input.name, input.price, input.engineType)
CarMap.cars[input.name] = car
return car
}
}
object CarMap {
val cars = HashMap<String, Car>()
}
We have created a function
to create a car
and another to get back the car
with the name
as input. But in case the car isn’t found, this will throw an exception, and that gets translated to the response shown:
我们创建了一个function
来创建car
并创建了另一个function
以name
作为输入取回car
。 但是如果找不到汽车,它将抛出异常,并将其转换为所示的响应:
{
"errors": [
{
"message": "Internal Server Error(s) while executing query"
}
],
"data": null
}
3.实施错误处理 (3. Implement error handling)
To be able to provide something sensible in case of an error or exception, we’ll add some more code.
为了能够在发生错误或异常时提供合理的信息,我们将添加更多代码。
open class CarNotFoundException(
errorMessage: String? = "",
private val parameters: Map<String, Any>? = mutableMapOf()
) : GraphQLException(errorMessage) {
override val message: String?
get() = super.message
override fun getExtensions(): MutableMap<String, Any> {
return mutableMapOf("parameters" to (parameters ?: mutableMapOf()))
}
override fun getErrorType(): ErrorClassification {
return ErrorType.DataFetchingException
}
}
Let’s understand what’s going on here.
让我们了解这里发生了什么。
- There’s a message field, which is self-explanatory. 有一个消息字段,它是不言自明的。
The overridden function,
getExtensions()
, is to provide a map of parameters. This could be different properties likeErrorCodes
that you want to pass for an error.重写的函数
getExtensions()
用于提供参数映射。 这可能是您要为错误传递的不同属性,例如ErrorCodes
。The overridden function,
getErrorTypes()
, is to provide a classification of the error — for example,DataFetchingException
orValidationException
.覆盖的函数
getErrorTypes()
用于提供错误的分类-例如,DataFetchingException
或ValidationException
。
In the above exception
, you can see it extends from GraphQLException
, which is the bridge between our custom CarNotFoundException
and GraphQLError
.
在上述exception
,您可以看到它是从GraphQLException
扩展的,它是我们的自定义CarNotFoundException
和GraphQLError
之间的桥梁。
Now we’re going to add GraphQLException
.
现在,我们将添加GraphQLException
。
When we add that, we need to make sure we create a separate Java module and add that GraphQLException
in the Java module.
添加时,我们需要确保创建一个单独的Java模块,并将该GraphQLException
添加到Java模块中。
Why?
为什么?
Because a Kotlin class can’t extend RuntimeException
and implement GraphQLError
at the same time. The reason for that can be found here.
因为Kotlin类无法扩展RuntimeException
并同时实现GraphQLError
。 原因可以在这里找到。
So now we create a GraphQLException
class.
因此,现在我们创建一个GraphQLException
类。
public class GraphQLException extends RuntimeException implements GraphQLError {
String customMessage;
public GraphQLException(String customMessage) {
this.customMessage = customMessage;
}
@Override
public String getMessage() {
return customMessage;
}
@Override
public List<SourceLocation> getLocations() {
return null;
}
@Override
public ErrorClassification getErrorType() {
return null;
}
}
And from the CarService
, we throw the exception when the car
isn’t found.
从CarService
,当找不到car
时,我们抛出异常。
@Service
class CarService {
fun getCar(input: String): Car {
return CarMap.cars[input]
?: throw CarNotFoundException("Car with name = $input not found",
mapOf("name" to input))
}
fun createCar(input: CarInput): Car {
val car = Car(input.name, input.price, input.engineType)
CarMap.cars[input.name] = car
return car
}
}
With that change, the response will contain a lot more detail than before.
有了这一更改,响应将包含比以前更多的细节。
{
"errors": [
{
"message":
"Exception while fetching data (/getCar) : Car with name = B Class not found",
"path": [
"getCar"
],
"extensions": {
"parameters": {
"name": "B Class"
},
"classification": "DataFetchingException"
}
}
],
"data": null
}
spring kotlin