为什么 GraphQL 难以流行?设计不足还是设计过度?
Source: Dev.to
I. 简化 GraphQL 服务开发
如果让 Zhipu Qingyan AI 使用 getBookById 函数编写一个 GraphQL 示例,它会生成如下代码:
@Configuration
public class GraphQLConfig {
@Bean
public GraphQLSchema graphQLSchema() {
String schema = "type Book { id: ID!, title: String, author: String }" +
"type Query { getBookById(id: ID!): Book }";
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder ->
builder.dataFetcher("getBookById", environment -> {
String bookId = environment.getArgument("id");
// Add logic to fetch the book here; this is just an example
return Book.builder().id(bookId)
.title("Sample Book Name").author("Sample Author").build();
}))
.build();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(
typeDefinitionRegistry, runtimeWiring);
}
}
这看起来相当复杂。graphql-java 库引入了模式解析、类型定义注册以及运行时 wiring 等概念。服务函数必须写成 DataFetcher,并通过 DataFetchingEnvironment 获取前端参数。然而,这种写法暴露了 GraphQL 引擎的底层实现,显得有些过时。
在最新的 Spring GraphQL 框架中,只需要几个简单的注解——框架会自动处理这些复杂性:
@Controller
public class BookController {
@QueryMapping
public Book getBookById(@Argument Long id) {
// ...
}
}
有了这种封装,业务代码只需要少量注解和 POJO;通常不需要使用内部的 graphql-java 接口。框架会自动分析 Java 类定义并生成 GraphQL 类型定义,省去了手动维护 schema 的步骤。
Quarkus 框架同样提供了成熟的 GraphQL 支持,并且在 Spring 之前就引入了类似的注解机制:
@ApplicationScoped
@GraphQLApi
public class BookService {
@Query("getBookById")
public Book getBookById(@Name("id") Long id) {
return ...
}
}
Nop 平台的 NopGraphQL 框架也使用注解来标记服务函数:
@BizModel("Book")
public class BookBizModel{
@BizQuery
public Book getBookById(@Name("id") Long id){
...
}
}
在基本的 GraphQL 服务支持之上,NopGraphQL 通过 CrudBizModel 与 NopORM 数据访问引擎深度集成,提供对常见 CRUD 操作(包括复杂的分页查询、子表过滤以及主从更新)的完整支持,通常无需额外代码。
详细介绍请参见文章《Nop 平台与 APIJSON 的功能对比》。
GraphQL 的设计比传统 Web 框架更纯粹;它不引入绑定于 Web 运行时环境的概念(如 Request 与 Response),因此很容易实现完全基于 POJO 的封装。不过,GraphQL 返回的是固定的 JSON 格式,无法直接实现文件上传/下载功能。NopGraphQL 为此新增了 /f/upload 扩展,使其语义更加完整。
观看 B 站视频:《Nop 平台如何为 GraphQL 增加文件上传/下载支持》。
II. 为 GraphQL 扩展返回状态码
GraphQL 协议的整体设计相当完整,尤其提供了大量内置的可扩展特性。Nop 平台利用 GraphQLResponse 中的 extensions map 来存放额外的返回状态码:
@DataBean
public class GraphQLResponseBean {
List errors;
Object data;
Map extensions;
@JsonIgnore
public String getErrorCode() {
return (String) getExtension("nop-error-code");
}
@JsonIgnore
public int getStatus() {
if (extensions == null)
return 0;
int defaultStatus = hasError() ? -1 : 0;
return ConvertHelper.toPrimitiveInt(
extensions.get("nop-status"),
defaultStatus,
NopException::new);
}
}
借助 GraphQL 指令作为扩展机制,NopGraphQL 框架还引入了更多面向业务的特性,以简化典型的应用服务。详情请参见《Nop 入门:如何创意性地扩展 GraphQL》。
III. GraphQL 与 REST 的等价性
表面上,GraphQL 提供了许多超越传统 REST 服务的高级特性。有趣的是,严格的理论分析表明,GraphQL 在数学上等价于在普通 REST 服务上额外加入一个用于选择返回字段的特殊 @selection 参数。Nop 平台建立了以下等价关系,使同一后端服务函数既可以通过 GraphQL 也可以通过 REST 访问。
query{
Book__get(id: 123) { name, title }
}
等价于
/r/Book__get?id=123&@selection=name,title
从这个视角看,GraphQL 只是一种 pull‑mode 的 REST 调用。在 Nop 平台的实现中……(内容未完)