在GraphQL-Java中,使operationName成为必需项
激励
当使用NewRelic或Datadog等APM产品对GraphQL API进行导入时,可能会遇到的问题是追踪名称的问题。
对于REST API,可以按照HTTP方法和路径(URL)对追踪进行分组,但是GraphQL仅凭端点的路径无法确定客户端想要执行的操作,通常需要查看查询的内容才能确定。
这里有一个在 GraphQL 中有用的操作名(operationName)的参数。
这个参数应该包含着客户端想要执行的操作的简洁名称(应该是的),所以我认为可以将其作为查询的标识符来使用。
在GraphQL.org上也有这样的说明。
从追踪性的角度来说,这非常方便。
从https://graphql.org/learn/queries/#operation-name 上得知:
操作名称是对操作的一个有意义且明确的名称。只有在多个操作的文档中才是必需的,但建议使用它,因为它对于调试和服务器端日志非常有帮助。
operationName 的规格是什么?
如果一个查询中包含多个操作,操作名是必需的。
通过修改任意项为必选项,将偏离GraphQL API的标准行为。
例如,如果通过GraphiQL等工具发出无名称查询,会导致错误,因此如果已经将API与该类生态系统结合使用,则在引入时需要进行确认。
如果是用于内部网络的API,可能可以进行调整,但如果要应用于公开API,则需要注意。
实施
不好意思廢話太多了,無論如何我都想要把operationName設為必需項目,所以我做了相應的實現。
如果使用了 Spring for GraphQL (spring-graphql),只需要将其作为 @Bean 进行注册即可实现功能。
(已在 spring-graphql v1.1.2 中确认)
package ore.exam.graphql;
import graphql.ErrorType;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.execution.AbortExecutionException;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.SimpleInstrumentation;
import graphql.execution.instrumentation.SimpleInstrumentationContext;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
/**
* オペレーション名 (<a href="https://graphql.org/learn/serving-over-http/#post-request">operationName</a>)
* を必須化する Instrumentation.<br>
* トレーサビリティの観点でオペレーション名が付いていた方がベターなので、それをクライアント側に強制する。
*
* @see <a href="https://graphql.org/learn/serving-over-http/#post-request">GraphQL HTTP Request
* specification</a>
* @see <a href="https://graphql.org/learn/queries/#operation-name">Operation name is very helpful
* for debugging and server-side logging.</a>
*/
public class RequiredOperationNameInstrumentation extends SimpleInstrumentation {
@Override
public InstrumentationContext<ExecutionResult> beginExecution(
InstrumentationExecutionParameters parameters) {
final String operationName = parameters.getOperation();
if (StringUtils.isBlank(operationName)) {
GraphQLError error =
GraphqlErrorBuilder.newError()
.errorType(ErrorType.ExecutionAborted)
.message(
"Anonymous queries are not supported. You must specify an `operationName` parameter.")
.build();
throw new AbortExecutionException(List.of(error));
}
return SimpleInstrumentationContext.noOp();
}
}
执行结果
### 無名クエリの場合
$ curl localhost:8080/graphql \
-H 'Content-Type: application/json' \
-d '{"query": "{foo{id}}"}' | jq .
{
"errors": [
{
"message": "Anonymous queries are not supported. You must specify an `operationName` parameter.",
"locations": [],
"extensions": {
"classification": "ExecutionAborted"
}
}
]
}
### マルチクエリで operationName 未指定の場合
$ curl localhost:8080/graphql \
-H 'Content-Type: application/json' \
-d '{"query": "query x {foo{id}} query y {foo{id}}", "operationName": ""}' | jq .
{
"errors": [
{
"message": "Anonymous queries are not supported. You must specify an `operationName` parameter.",
"locations": [],
"extensions": {
"classification": "ExecutionAborted"
}
}
]
}
总结
我已经介绍了如何实现一个GraphQL客户端的Instrumentation来强制指定operationName的功能。