几乎所有流行的编程语言都有 GraphQL 实现。
微信搜索关注《Java学研大本营》,加入读者群,分享更多精彩
今天,几乎所有流行的编程语言都有 GraphQL 实现。这些解决方案可以安全使用,而且大多是经过时间检验、可用于生产的。
应该首先将注意力集中在 Graphql-java 库上。这是唯一可用的真实 GraphQL 引擎。
该引擎已经能够实现数据获取、处理上下文、处理错误、监控、查询限制、字段可见性,甚至是数据加载器。因此,可以选择按原样使用它,或者对框架进行大胆的更改,看看哪个最适合。Graphql-java 是开源的,由普通人创建,最近一次提交只是几天前。该引擎正在积极开发中。
但是,尽管有这么多优点,还是应该仔细考虑是否值得直接使用它。
除了这个库之外,还有其他三个需要考虑的框架。其他一切主要由非常小的库组成。
但首先,看一下在后端设计 graphql API 的两种关键方法。有两个对立的阵营——模式优先和代码优先解决方案。
在经典的模式优先方法中,首先描述 graphql 模式,然后在代码中使用它来实现模型和数据获取器。这种方法的优点是不同的人甚至部门都可以设计和开发方案——例如,分析师设计方案,开发人员实施。也可以很方便的写一个方案马上给客户,同时开发一个后端。缺点是需要同时实现模式和代码——开发 API 时可能需要更多时间 + 现在有 2 个源不能相互冲突并完全同步——一个额外的链接可能会中断。
使用代码优先的方法,我们只编写代码并基于注释,框架本身生成模式。在这里,我们只有 1 个事实来源,但是没有代码就无法构建 graphql 图。
我们要关注的第一个框架是 DGS (Domain Graph Service)。
Netflix 最初是在 2019 年发明的,并于 2020 年发布在开源上。这是一个成熟的框架——它有助于处理 GraphQL 代码、编写单元测试、提供自己的错误处理、基于模式生成数据获取器的代码生成等等。这是一个模式优先的解决方案。这一切都已准备就绪,Netflix 正在充分利用它。
尽管如此,我们还是选择了不同的解决方案。
首先,DGS 是模式优先的,我们希望使用代码优先的方法,更容易提出,开发速度更快,不需要开发没有代码的模式。
其次,DGS 使用spring boot。没关系!但我们不在公司内部使用它——我们有自己的框架,它使用纯 spring-core。当然,这并不意味着不可能提高它——我们设法开始了,之前与保罗讨论过是否在没有引导的情况下提高规范或作者不推荐(规范) . 但要做到这一点,有必要了解框架本身的代码,手动查找和声明十几个未记录且并不总是可以理解的 bin,这些 bin 在新版本的 DGS 中可能会被破坏。一般来说,不是免费维护的。
第三,即使它是一个成熟的框架,你仍然必须添加它来处理单元测试、错误处理、监控等。这仅仅是因为你的项目正在增长,而你将没有足够的现有解决方案。
尽管如此,它还是很酷的。
DGS:
模式优先
来自 Netflix 的开源
在Spring-boot启动
成熟的框架
我们将分析的下一个liba是 Java SPQR 。
经过多年验证的开源库。此外,这也是唯一的代码优先解决方案,而且不是一个成熟的框架。liba 所做的所有事情都是实现代码优先的方法,并帮助稍微使用服务 GraphQL 代码。
但是,尽管做出了选择,但目前很难建议使用它,因为它已被放弃。最后一次提交是一年多前,问题没有答案,也没有支持。
为什么这可能很重要,例如,graphql 支持继承,并且在 2020 年,graphql-spec,然后是 graphql-java,获得了使用多接口继承的能力。现在是 2022 年,但在 SPQR 中不能使用这个新功能。
然而,最近该项目有了恢复工作的计划,这不禁让人欣喜若狂。
要谈论的最后一个框架是 Spring GraphQL 。
2021 年 7 月发布。同样是模式优先的方法,与 spring 的集成,稍微重复了 DGS。也有自己的错误处理程序,支持编写单元测试,更方便地使用数据获取器。
模式优先
Spring集成
成熟的框架
最近发布
现在创建一个简单的 graphql 服务器。作为标准堆栈,我们将使用 Java 和 Spring,作为 GraphQL - SPQR,使用 Graphql-java 引擎。
首先,让我们创建将执行所有查询的GraphQL bin。
@Configuration
public class GraphQLConfig {
private final CandidateResolver candidateResolver;
private final ResumeResolver resumeResolver;
public GraphQLConfig(CandidateResolver candidateResolver,
ResumeResolver resumeResolver) {
this.candidateResolver = candidateResolver;
this.resumeResolver = resumeResolver;
}
@Bean
public GraphQLSchema getGraphQLSchema() {
return new GraphQLSchemaGenerator()
.withBasePackages("com.example.graphql.demo.models")
.withOperationsFromSingletons(candidateResolver, resumeResolver)
.generate();
}
@Bean
public GraphQL getGraphQL(GraphQLSchema graphQLSchema) {
return GraphQL.newGraphQL(graphQLSchema)
.queryExecutionStrategy(new AsyncExecutionStrategy())
.instrumentation(new CustomTracingInstrumentation())
.build();
}
}
要执行它,需要知道模式。但由于 SPQR 是一种代码优先的方法,我们使用一个模式生成器,将从根包中的模型字段构建它。
接下来,我们将定义一个 graphql 查询执行策略。默认情况下,图中的每个节点都是异步执行的,以防万一可以更改。
之后,让我们重新定义工具(我们将分别讨论)并运行 bin.GraphQLSchemaExecutionStrategyAsyncExecutionStrategy。
需要从某个地方获取请求,所以创建一个接受查询的常规 POST 方法。对于所有 graphql 请求,都是相同的,不像 REST,我们为每个请求创建了一个单独的方法。
然后将执行请求传递给 graphql bin。
@RestController
public class DemoController {
private final GraphQL graphQL;
@Autowired
DemoController(GraphQL graphQL) {
this.graphQL = graphQL;
}
@PostMapping(path = "graphql",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ExecutionResult graphql(@RequestBody EntryPoint entryPoint) {
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(entryPoint.query)
.build();
return graphQL.execute(executionInput);
}
public static class EntryPoint {
public String query;
}
}
已经描述了一个模式,知道如何接受查询。但是在哪里描述这个图的入口点?数据提取器(或解析器)在 graphql 中负责这一点。将在其中描述图节点的 bean。
@GraphQLQuery(name = "candidates") public CompletableFuture
> getCandidates() { return CompletableFuture.supplyAsync(candidateService::getCandidates); }
在这种情况下,我们创建了一个入口点,它返回一些 .candidatesCandidate 的模型
public class Candidate {
private Integer id;
private String firstName;
private String lastName;
private String email;
private String phone;
// getters and setters are omitted
}
此外,SPQR 将在解析器中的模型上构建方案。
当然,尽可能多的这样的节点是可能的,也是必要的,这样它们才能相互交织,形成一个图。因此,让我们创建另一个节点并使用 . resumes@GraphQLContext。
@GraphQLQuery(name = "resumes")
public CompletableFuture> getResumes(@GraphQLContext Candidate candidate) {
return CompletableFuture.supplyAsync(() -> resumeService.getResumes(candidate));
}
public class Resume {
private Integer id;
private String lastExperience;
private Salary salary;
// getters and setters are omitted
}
public class Salary {
private String currency;
private Integer amount;
// getters and setters are omitted
}
它的工作原理是这样的——如果你从 请求某些东西,那么这个解析器才会对.candidatesresumes起作用。
除此之外,我们当然希望监控查询执行的状态:每个解析器执行了多长时间,完整请求执行了多长时间,可以捕获哪些错误。为此,在注册 graphql-bin 时,可以指定 Instrumentation — 默认和自定义。
从技术上讲,这是一个实现的类(在例子中,继承自一个常规存根,以便不实现所有方法)。
它说明了在请求的某种状态下调用的方法:请求刚开始执行时,解析器被调用时,执行结束时等。
public class CustomTracingInstrumentation extends SimpleInstrumentation {
Logger logger = LoggerFactory.getLogger(CustomTracingInstrumentation.class);
static class TracingState implements InstrumentationState {
long startTime;
}
// Cоздаём контекст трэйсинга для конкретного запроса
@Override
public InstrumentationState createState() {
return new TracingState();
}
// Выполняется перед каждым запросом. Инициализируем контекст трейсинга для замеров времени выполнения
@Override
public InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) {
TracingState tracingState = parameters.getInstrumentationState();
tracingState.startTime = System.currentTimeMillis();
return super.beginExecution(parameters);
}
// Выполняется при завершении запроса. С помощью totalTime мерим время выполнения всего запроса
@Override
public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) {
TracingState tracingState = parameters.getInstrumentationState();
long totalTime = System.currentTimeMillis() - tracingState.startTime;
logger.info("Total execution time: {} ms", totalTime);
return super.instrumentExecutionResult(executionResult, parameters);
}
// Выполняется при каждом вызове DataFetcher/Resolver. С помощью него будем мерить время выполнения каждого резолвера
@Override
public DataFetcher> instrumentDataFetcher(DataFetcher> dataFetcher, InstrumentationFieldFetchParameters parameters) {
// Так как любое поле в графе потенциально может быть резолвером, оставим только те, которые хотя бы что-то делают
if (parameters.isTrivialDataFetcher()) {
return dataFetcher;
}
return environment {
long startTime = System.currentTimeMillis();
Object result = dataFetcher.get(environment);
// Так как все ноды в нашем случае выполняются асинхронно, замерим время только для них
if(result instanceof CompletableFuture) {
((CompletableFuture>) result).whenComplete((r, ex); {
long totalTime = System.currentTimeMillis() - startTime;
logger.info("Resolver {} took {} ms", findResolverTag(parameters), totalTime);
});
}
return result;
};
}
// Ветьеватая логика получения имени резолвера и его родителя (для лучшего понимания откуда вызывалась нода)
private String findResolverTag(InstrumentationFieldFetchParameters parameters) {
GraphQLOutputType type = parameters.getExecutionStepInfo().getParent().getType();
GraphQLObjectType parent;
if (type instanceof GraphQLNonNull) {
parent = (GraphQLObjectType) ((GraphQLNonNull) type).getWrappedType();
} else {
parent = (GraphQLObjectType) type;
}
return parent.getName() + "." + parameters.getExecutionStepInfo().getPath().getSegmentName();
}
}
事实上,Instrumentation 是非常强大的功能,不仅可以用于监控。例如,已经从 graphql-java 实现的 graphql-java 测量查询的最大深度并在超过时取消查询,并且在帮助下您可以将权重分配给特定节点并控制查询的复杂性,但存在细微差别。
这足以启动我们的服务。
请求本身。
{
candidates {
id,
firstName,
lastName,
phone,
email,
resumes {
id,
lastExperience,
salary {
currency,
amount
}
}
}
}
响应将采用服务的标准 json 格式。
这是 java 中 graphql 的情况。我们查看了不同的框架,评估了它们的优缺点,然后用 Java 实现了一个简单的 graphql 服务。
购买链接:https://item.jd.com/13495830.html
《Java编程讲义》根据目前Java开发领域的实际需求,从初学者角度出发,详细讲解了Java技术的基础知识。
全书共15章,包括Java开发入门,Java语言基础,Java控制结构,数组,面向对象编程,继承和多态,抽象类、接口和内部类,异常处理,Java常用类库,集合与泛型,Lambda表达式,输入-输出流,多线程,JDBC数据库技术,网络编程等内容。内容全面覆盖.1ava开发必备的基础知识点,结合生活化案例展开讲解,程序代码给出了详细的注释,能够使初学者轻松领会Java技术精髓,快速掌握Java开发技能。
《Java编程讲义》适合作为高等院校相关专业的教材及教学参考书,也适合作为Java开发入门者的自学用书,还可供开发人员查阅、参考。
精彩回顾
想要代码干净又整洁?这里有十大原则
详细&全面的RxJava架构原理与设计讲解
深入理解Docker网络通信原理
微信搜索关注《Java学研大本营》
访问【IT今日热榜】,发现每日技术热点