HotChocolate 上的更多高级 GraphQL 概念






3.21/5 (4投票s)
使用 HotChocolate 深入了解 GraphQL 的更高级概念
引言
在之前的一系列文章中,我们广泛探讨了 GraphQL 的本质及其在 2010 年代初由 Facebook 提出的原因。具体来说,GraphQL 使我们能够精确地检索所需数据,不多也不少,同时避免了大量端点的需求。有关详细说明,请参阅此处:使用 HotChocolate 构建 GraphQL API
虽然我们简要演示了一个使用 HotChocolate 和 JavaScript 客户端的示例,展示了 GraphQL 只提供所请求有效负载的能力,但我们没有深入探讨 GraphQL 的高级复杂性。因此,本系列的重点是通过深入研究规范中最复杂的部分来弥补这一差距。我们的目标是全面阐述最复杂的方面。
以下书籍对于完成本系列很有帮助
- 学习 GraphQL:现代 Web 应用的声明式数据获取 (Porcello, Banks)
- .NET 8 应用和服务:使用 Blazor、.NET MAUI、gRPC、GraphQL 等企业技术构建实用项目 (Price)
本文最初发布于此
什么是 GraphQL?
GraphQL 起源于 Facebook 在 2010 年初的开发工作,主要目标是优化网络数据传输,特别是对于带宽效率至关重要的移动应用程序。随后,它被正式化为服务器必须遵守的**规范**,以满足合同要求。这种灵活的方法允许 GraphQL 服务器在几乎任何编程语言中实现,并且在同一种语言中发现多种实现并不罕见。
非常重要
必须强调的是,GraphQL 本身只是一项**规范**。因此,利用此技术的 API 需要部署在符合此规范并能够理解和处理以相应语言编写的请求的服务器上。实际上,GraphQL 服务器通常通过安装为 GraphQL 实现量身定制的库或运行时环境来实例化。
HotChocolate 是 GraphQL 规范的 C# 实现的一个示例。
有关更多详细信息,请参阅
什么是 HotChocolate?
HotChocolate 是 Microsoft .NET 平台的开源 GraphQL 服务器,符合最新的 GraphQL 2021 年 10 月规范 + 草案,这使得 Hot Chocolate 与所有符合 GraphQL 的客户端兼容。
https://chillicream.com/docs/hotchocolate/v13
我们将使用 HotChocolate 在 Azure Function 中设置一个基本的 GraphQL 服务器,然后使用 JavaScript 查询它。
创建环境
-
创建一个名为
EOCS.GraphQLAdvanced
的新解决方案(例如),并在其中创建一个新的 Azure Functions 项目。 -
创建一个名为 Customer.cs 的新类,并在此处添加以下代码
public class Customer { public string Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
- 创建一个名为
ICustomerRepository
的新接口,并在此处添加以下代码public interface ICustomerRepository { List<Customer> GetAllCustomers(); Customer GetCustomerById(string id); }
- 创建一个名为
MockCustomerRepository
的新类,实现ICustomerRepository
接口。public class MockCustomerRepository : ICustomerRepository { public List<Customer> GetAllCustomers() { return new List<Customer>() { new Customer(){ Id = "0001", Name = "Bruce Smith", Age = 45 }, new Customer(){ Id = "0010", Name = "Melissa Price", Age = 52 } }; } public Customer GetCustomerById(string id) { return new Customer(){ Id = "0001", Name = "Bruce Smith", Age = 45 }; } }
-
添加
HotChocolate.AzureFunctions
NuGet 包 -
创建一个名为 CustomerService.cs 的类(或更通用的名称),并在此处添加以下代码
public class CustomerService { private readonly IGraphQLRequestExecutor _executor; public CustomerService(IGraphQLRequestExecutor executor) { _executor = executor; } [FunctionName(nameof(Run))] public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "graphql/{**slug}")] HttpRequest req) { return await _executor.ExecuteAsync(req); } }
- 创建一个名为 StartUp.cs 的类来引导 Azure Function。
public class StartUp : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) { ConfigureServices(builder.Services); } private static void ConfigureServices(IServiceCollection services) { services.AddSingleton<ICustomerRepository, MockCustomerRepository>(); } }
非常重要
此设置目前无法正常运行;它仅仅为未来的开发奠定了基础。
使用服务
现在是时候使用我们的服务了,为此我们将创建一个简单的 HTML 文件。
-
创建一个新的 ASP.NET Core Web App 项目,例如命名为
EOCS.GraphQLAdvanced.Client
,并在 wwwroot 中添加一个名为 indexQuery.html 的文件。 -
在此处添加以下代码
<html> <head> <title>GraphQL</title> </head> <body> <pre><code class="language-json" id="code"></code></pre> <script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.9/beautify.min.js"></script> </body> </html>
- 编辑 Program.cs 代码
public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles(); app.Run(async (context) => { await context.Response.WriteAsync ("Request Handled and Response Generated"); }); app.Run(); } }
重要
请确保在启动时设置多个项目。
另外,请确保通过修改 host.json 文件来授权 CORS
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
},
"Host": {
"CORS": "*"
}
}
信息
我们的基础代码相当简单,主要包括一个请求,用于检索 customers
列表或单个 customer
。虽然看起来并不起眼,但这种基本设置是演示更高级概念的合适起点。
必须再次强调:GraphQL 作为一项规范运行,需要在服务器端安装或实现运行时。因此,客户端不能仅仅被动等待数据;相反,它承担了积极的角色,并且必须明确地阐述其数据需求。本节将阐明这一过程在 GraphQL 规范中的定义方式,详细说明客户端如何指定其请求。
SDL 是什么意思?
GraphQL 规范引入了其独特的类型语言,称为 Schema Definition Language (SDL),它是用于创建 GraphQL schema 的媒介。例如,一个客户在 SDL 中将定义如下。
type Customer {
id: ID!
name: String
age: Int
}
在此代码片段中,我们定义了一个 Customer
类型,包含三个字段:一个 ID
类型,一个代表字母数字数据的 String
类型,以及一个表示整数的 Int
类型。值得注意的是,我们通过在其后附加感叹号 (!
) 来指定 Id
字段是强制性的。
信息
ID
类型解析为 string
,但需要一个唯一值。
类似地,我们可以定义一个 Order
类型来表示电子商务应用中的订单。它可能定义如下
type Order {
id: ID!
reference: String!
amount: Float
customerId: ID!
}
然而,orders
和 customers
并不是互斥的实体;customers
可以有多个订单,而一个 order
通常与一个特定的 customer
相关联。因此,我们可以增强模型,如下所示
type Customer {
id: ID!
name: String
age: Int
orders: [Order]
}
type Order {
id: ID!
reference: String!
amount: Float
customer: Customer
}
在这里,我们指定一个 customer
可以拥有一个订单数组,用方括号 [
和 ]
表示。
如何对这些类型执行查询?
Customer
和 Order
类型本身不为客户端应用程序提供任何功能;它们仅勾勒出可用实体的结构。要执行查询,GraphQL 规范要求包含 Query 根类型。
信息
根类型是什么意思?它们是规范中概述的固有类型,可以被视为任何 GraphQL API 的入口点。本质上,有三种不同的根类型可用
- Query(用于数据检索)
- Mutation(用于数据修改),以及
- 订阅
为了使客户端能够检索所有 customers
或通过 ID
检索特定 customer
,我们必须定义一个 Query 类型,如下所示。
type Query {
customers: Customer
customer(id:ID!): Customer
}
此时,服务器已完成其任务,客户端承担责任。客户端不再可以简单地访问一个端点并被动等待响应;它必须主动指定其所需数据。这可以通过 GET
或 POST
请求来完成。
信息
如我们先前系列中所述,无需大量端点。实际上,一个单一的端点足以满足需求,通常表示为 /graphql
。我们将相应地遵循此指导。
使用 POST 请求进行查询
标准的 GraphQL POST
请求应使用 application/json 内容类型,并包含以下形式的 JSON 编码体。
POST http://<path_api>/graphql
{
"query": "query customer (id: "123f0") { id, age, orders { reference } }",
}
根据 GraphQL 规范,响应将由格式化的 JSON 有效负载组成。
{
"data": {
"customer": {
"id": "123f0",
"age": "45",
"orders": []
}
}
}
信息
如果响应包含错误,GraphQL 将提供相应的格式化消息。
{
"errors": [
{
"message": "<...>",
"locations": [
{
"line": 2,
"column": 1
}
]
}
]
}
使用 GET 请求进行查询
执行 GET
请求也是可行的,但它往往更麻烦;因此,与 POST
请求相比,它的使用较少。对于感兴趣的读者,我们建议参考官方文档以获取更多详细信息。
什么是解析器?
到目前为止,我们仅在 SDL 中概述了可用的类型和查询。但是,我们还没有解决如何从任何数据存储中检索字段。**解析器就是这样**:它们充当规范(定义我们想要的结构)和具体实现(详细说明如何获取它)之间的桥梁。
信息
GraphQL 规范没有提供关于如何实现解析器的确切指南。它主要强调此概念必须存在于库中并且是可扩展的。
我们不会深入探讨这个概念,因为它对大多数开发人员来说似乎是直观的:最终,API 要想正常运行,必须在某个时候从数据存储中收集数据。**解析器只是一个理解如何提取某些数据的函数。**
现在我们已经理解了规范中的查询和解析器的概念,让我们探讨一下它在实践中是如何具体实现的。
SDL 如何与 HotChocolate 集成?
信息
此实现完全特定于 HotChocolate
,其他库可能以不同的方式实现查询和解析器。
实现解析器
回想上一篇文章,我们使用了 MockCustomerRepository
类来与 customers
交互。
public class MockCustomerRepository : ICustomerRepository
{
public List<Customer> GetAllCustomers()
{
return new List<Customer>()
{
new Customer(){ Id = "0001", Name = "Bruce Smith", Age = 45 },
new Customer(){ Id = "0010", Name = "Melissa Price", Age = 52 }
};
}
public Customer GetCustomerById(string id)
{
return new Customer(){ Id = "0001", Name = "Bruce Smith", Age = 45 };
}
}
如果到目前为止一切都已理解,那么这些方法将作为解析器,并且需要在某个时候调用它们来检索数据。
实现 Query 类型
在探索如何使用查询检索数据之前,我们需要了解 SDL 类型如何在 C# 中表示。HotChocolate
只需要创建简单的 POCO(普通 CLR 对象)类,如下所示
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public string Id { get; set; }
public string Reference { get; set; }
public decimal Amount { get; set; }
public Customer Customer { get; set; }
}
有了这些类之后,我们就可以执行查询了。为此,HotChocolate
要求定义一个 Query
类并将所有组件(POCO 类和解析器)集成在一起。
public class Query
{
public List<Customer> GetCustomers([Service] ICustomerRepository customerRepository)
{
return customerRepository.GetAllCustomers();
}
public Customer GetCustomerById
([Service] ICustomerRepository customerRepository, string id)
{
return customerRepository.GetCustomerById(id);
}
}
在这里,我们观察到解析器通过依赖注入使用 [Service]
注释获得。但是,值得注意的是,这并不是管理解析器的唯一方法,也不是最常见的方法。有关更多详细信息,请参阅文档。
配置完所有组件后,我们需要修改 StartUp
类,指示服务器遵守 GraphQL 规范。
public class StartUp : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
ConfigureServices(builder.Services);
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICustomerRepository, MockCustomerRepository>();
services.AddGraphQLFunction().AddQueryType<Query>();
}
}
信息
此示例说明了 HotChocolate 如何通过允许我们使用原生类轻松定义 GraphQL 类型来简化我们的任务。在后台,HotChocolate
通过遵守 GraphQL 规范来处理繁重的工作。
使用服务
我们现在将转到客户端项目。
- 编辑 indexQuery.html 文件
<html> <head> <title>GraphQL</title> </head> <body> <pre><code class="language-json" id="code"></code></pre> <script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.9/beautify.min.js"></script> <script> (async function () { const data = JSON.stringify({ query: `query { customerById(id:"0010") { id name age } }` }); const response = await fetch( 'https://:7132/api/graphql', { method: 'post', body: data, headers: { 'Content-Type': 'application/json' }, } ); const json = await response.json(); document.getElementById('code').innerHTML = js_beautify( JSON.stringify(json.data) ); })(); </script> </body> </html>
重要
在前面的示例中,我们注意到 JavaScript 中调用的方法名是
customerById
。HotChocolate
使用这种命名约定:服务器期望找到一个以customerById
结尾的解析器(不区分大小写)。 - 运行程序
导航到 indexQuery.html 文件并观察结果。
很明显,这些机制运行正常,并且正在调用正确的解析器。
到此为止,我们关于 Query 类型的讨论就结束了,但这仅仅是旅程的开始。接下来,我们将深入探讨如何使用 GraphQL 修改数据。但是为了避免使本文过于冗长,有兴趣了解此实现的读者可以在此处找到后续内容。
历史
- 2024 年 3 月 7 日:初始版本