目录
介绍
服务如何运作?
GraphQL的用法和优化
组成和结构
如何运行?
前提条件(对于Windows)
行动顺序
使用Playground的查询和变异
测验
结论
本文和代码说明了.NET 5中GraphQL的用法。GraphQL数据访问已通过数据缓存进行了优化。开发了一些库来支持GraphQL、JWT身份验证、TLS、可配置日志记录。
- 下载源代码242.9 KB
本文介绍了两个.NET 5 Web服务。第一个GraphQlService支持使用GraphQL技术通过数据库(SQL Server)创建、检索、更新和删除(CRUD)操作。传输层安全性(TLS)保护消息在跨网络传输时不被读取,并且使用JSON Web令牌(JWT)进行用户身份验证和授权。第二种LoginService提供用户登录机制,并根据用户的凭证生成JWT。
维基:
GraphQL是一种用于API的开源数据查询和操作语言,并且是用于使用现有数据执行查询的运行时。GraphQL由Facebook在2012年内部开发,并于2015年公开发布。目前,GraphQL项目由GraphQL Foundation运行。它提供了一种开发Web API的方法,并且已与REST和其他Web服务体系结构进行了比较和对比。它允许客户端定义所需数据的结构,并从服务器返回相同的数据结构,因此可以防止返回过多的数据。
本文的代码演示了以下主要功能:
- 使用GraphQL技术的交易数据存储库的CRUD操作
- 方便的Playground和GraphiQL现成的Web UI应用程序,用于GraphQL查询和变异,不需要前端代码
- JWT认证
- OpenApi(又名Swagger)与GraphQL结合使用
- 灵活的可配置日志记录(当前已配置为仅向控制台提供一些最小输出)
- 使用内存服务进行集成测试
使用了几个使用NuGet进行GraphQL开发的开源软件包。
服务如何运作?服务的工作如下图所示:
图1.服务工作
要开始她/他的工作,用户需要向LoginService (1)提供凭据(用户名和密码)。后者生成JWT并将其返回给用户。然后,用户向GraphQlService (2) 发送查询/更新,并从服务获得响应。
这些服务具有单独的数据库。LoginService访问的用户数据库包UsersDb含一个Users表,该表由用户名、密码(在现实世界中加密)和每个用户的角色组成。GraphQlService访问的人员数据库PersonsDb包含几个与人员、组织及其关系和隶属关系有关的表。
GraphQL的用法和优化GraphQL定义了客户端和服务器之间的约定,用于数据检索(查询)和更新(变异)。查询和变异都构成类似JSON的结构。检索到的数据被格式化为与请求几乎相同的结构,然后返回给客户端。由于GraphQL查询的分层形式,数据检索的过程是对嵌套字段的处理程序的一系列调用。
GraphQL暗示对每个数据字段使用解析函数(解析器)。通常,包括此处使用的GraphQL的实现可确保在Web响应层次结构形成期间调用适当的解析器。如果每个解析器向数据库发出SELECT查询,则这些查询的总数等于层次结构每个级别上的返回行之和。考虑一个获取所有人的查询。在最高级别,所有n个人都被提取。然后在第二级,SELECT查询被执行Ñ是时候获取每个人的隶属关系。在以下每个级别上观察到相似的图片。显然,大量的返回行导致对数据库的大量查询,从而导致严重的性能损失。在这种情况下,对数据库的查询数为:
此问题通常称为N + 1查询问题。
高效的GraphQL实现必须为该问题提供合理的解决方案。在这项工作中实现的解决方案可以表述如下。在“天真的”实现中,每个字段的处理程序都调用数据库来检索数据。在优化的解决方案中,每个级别上的字段处理程序的第一次调用都会从数据库数据中检索该级别上所有字段的信息,并将它们存储在附加到GraphQL上下文对象的缓存中。GraphQL上下文对象可用于所有字段处理程序。给定级别字段处理程序的后续调用从缓存而不是从数据库获取数据。在优化的情况下,对数据库的查询数为:
database_queries = levels
如您所见,数据库调用的数量(SELECT -s)与GraphQL查询的内部级别相对应,并且与每个级别上提取的记录的数量无关。
差异在下面的图2和图3中说明:
图2.未优化的数据提取
图3.优化的数据获取
解析程序的返回值将自动插入到GraphQL查询的响应对象中。我们仅需要提供该级别的解析器中已经从数据库中获取的数据的返回值。实现此目的的最简单方法是将获取的数据放入内存中的缓存对象,并将其附加到所有解析程序可用的上下文对象中。根据解析器,将缓存组织为具有关键字的字典。每个解析器都使用适当的密钥返回从缓存中提取的一条数据。
这些返回值形成响应对象,以完成GraphQL查询后发送回客户端。由于缓存对象是上下文对象的属性,因此在GraphQL查询处理结束时,它将与上下文一起销毁。因此,为每个客户端请求创建了缓存对象,并且缓存对象的生存期受到此请求处理的限制。
基于内存缓存的数据采集优化可提高性能。但是,它的限制在于可用操作内存(RAM)的大小。如果缓存对象太大而无法容纳在单个进程内存中,则可以使用分布式缓存解决方案,例如Redis,Memcached或类似的缓存。在本文中,我们假设简单的内存高速缓存可以满足绝大多数实际情况。
组成和结构成分
项目类型
地点
描述
GraphQlService
服务(控制台应用程序)
.\
该服务使用GraphQL技术执行CRUD操作。它提供了两个控制器。GqlController处理所有的GraphQL请求,而PersonController处理无参数的GET请求和一些预定义的文本,以及另一个以Person id作为参数的GET请求。该请求在内部作为具有硬编码查询的普通GraphQL请求进行处理。它是常用GraphQL查询的“捷径”。在此,PersonController主要用于说明目的。
LoginService
服务(控制台应用程序)
.\
该服务支持用户登录过程。它具有一个LoginController创建JWT以响应用户的凭据。
ServicesTest
测试项目(控制台应用程序)
.\Tests
Project为这两种服务都提供集成测试。这些测试基于内存服务的概念。这种方法使开发人员可以轻松测试实际的服务代码。
ConsoleClient
控制台应用
.\
用于服务的简单客户端控制台应用程序。
PersonModelLib
动态链接库
.\Model
该项目提供了针对给定领域问题的特定代码(在我们的案例中是Persons)。
AsyncLockLib
动态链接库
.\Libs
提供异步/等待方法的锁定机制,特别适用于GraphQL缓存的实现。
AuthRolesLib
动态链接库
.\Libs
提供enum UserAuthRole。
GraphQlHelperLib
动态链接库
.\Libs
包含与GraphQL相关的常规代码,其中包括一个用于数据缓存的代码,用于解决N + 1个查询问题。
HttpClientLib
动态链接库
.\Libs
用于创建HTTP客户端,实现HttpClientWrapper类。
JwtAuthLib
动态链接库
.\Libs
通过用户的凭证生成JWT
JwtLoginLib
动态链接库
.\Libs
提供用户登录处理,使用JwtAuthLib。
RepoInterfaceLib
动态链接库
.\Libs
定义IRepo用于处理事务数据存储库的接口。
RepoLib
动态链接库
.\Libs
为EntityFrameworkCore实现RepoInterfacesLib中的IRepo接口。它为数据保存程序配备了事务处理。
如何运行? 前提条件(对于Windows)- 本地SQL Server(请参阅服务的文件appsettings.json中的连接字符串)
- 带有.NET 5支持的Visual Studio 2019(VS2019)
- Postman应用程序通过身份验证测试用例
1、使用支持.NET 5的VS2019打开解决方案GraphQL_DotNet.sln并构建解决方案。
2、使用SQL Server。为了简单起见,采用代码优先范式。在运行适当的服务或其集成测试时,会自动创建数据库UsersDb和PersonsDb。请调整appsettings.json服务配置文件中的连接字符串(如果需要)。首先,数据库中填充了代码中的多个初始记录。为了确保身份机制正常运行,所有这些记录均分配了负Id-s,但UsersDb.Users表不会在本工作中以编程方式更改。
3、GraphQlService的配置文件appsetting.json包含对象FeatureToggles:
"FeatureToggles": {
"IsAuthJwt": true,
"IsOpenApiSwagger": true,
"IsGraphIql": true,
"IsGraphQLPlayground": true,
"IsGraphQLSchema": true
}
默认情况下,所有选项均设置为true。让我们先从没有身份验证的状态开始,然后将其"IsAuthJwt"设置为false。
4、开始GraphQlService。它可以从VS2019作为服务或在IIS Express下进行。具有GraphQL的带有Playground Web UI应用程序的浏览器将自动启动。
在Playground网页中,您可能会看到GraphQL模式,并使用不同的查询和变异。可以从文件querys-mutations-examples.txt复制一些预定义的查询和变异。
图4. Playground Web应用程序
您可以使用类似的GraphiQL Web应用程序代替Playground:在https://localhost:5001/graphiql上浏览。
图5. GraphiQL Web应用程序
5、Playground应用程序使用中间件来绕过响应GqlController(通常在开发过程中使用,但是在该项目中,所有版本都可用)。它不会调用客户在生产中使用的那个GqlController。要使用GqlController,您可以使用Postman应用程序。
在Postman中,使POST到https://localhost:5001/gql与Body-> GraphQL在QUERY文本框中提供您实际的GraphQL查询/变异的。
图6.使用Postman进行GraphQL查询
6、您也可以使用OpenApi(aka Swagger):浏览至https://localhost:5001/swagger:
图7. OpenApi(Swagger)
在Swagger网页中激活POST/Gql。
然后在Postman中,按右上角的Code链接:
图8. Postman的HTTP请求。
将查询复制到Swagger的Request body文本框并执行方法。
图9. POST/Gql请求。
图10. POST/Gql响应。
7、在所有情况下,您都可以使用不安全的调用方式进行http://localhost:5000 的说明和调试。
8、现在让我们使用JWT身份验证。停止运行GraphQlService(如果是),在GraphQlService的配置文件appsetting.json中的对象FeatureToggle设置"IsAuthJwt"为true,在VS2019,定义LoginService和GraphQlService为多启动项目并运行它们。
或者,可以通过从相应的Debug或Release目录中激活文件LoginService.exe和GraphQlService.exe来启动服务。在这种情况下,应在服务已经运行时手动启动浏览器在https://localhost:5001/playground上导航。
首先,您需要从Postman向https://localhost:5011/login发生一个POST,提供用户凭据username = "Super", password = "SuperPassword"。请注意端口5011:如您所见,LoginService监听该端口。
图11:登录
然后在Postman中打开一个新选项卡,以POST到https://localhost:5001/gql,打开Authorization-> Bearer Token,将登录时收到的令牌复制到Token文本框中,并通过单击Send按钮post。您可以使用OpenApi身份验证。为此,在OpenApi Web页面中, 按一下按钮Authorize(请参见图7),在Value文本框中插入单词“Bearer”,然后插入JWT令牌,然后按Authorize按钮。
9、集成测试可以在目录\\Test中的ServicesServiceTest项目中找到。
使用Playground的查询和变异Playground是一个Web应用程序,可以由GraphQL库中间件立即激活(在这个例子中,使用NuGet包GraphQL.Server.Ui.Playground)。它提供了便捷、直观的方式来定义,记录和执行GraphQL查询和变异。Playground提供智能感知、错误处理和单词提示。它还显示了GraphQL模式以及可用于给定任务的所有查询和变异。Playground的屏幕截图如上图4所示。
这些是我们解决方案的查询和变异示例。您可能会在Playground DOCS窗格中看到其描述。
以下是Persons查询返回所有人。
query Persons {
personQuery {
persons {
id
givenName
surname
affiliations {
organization {
name
parent {
name
}
}
role {
name
}
}
relations {
p2 {
givenName
surname
}
kind
notes
}
}
}
}
查询PersonById通过其唯一id参数返回一个人。在以下示例中,id将设置为1。
query PersonById {
personByIdQuery {
personById(id: 1) {
id
givenName
surname
relations {
p2 {
id
givenName
surname
}
kind
}
affiliations {
organization {
name
}
role {
name
}
}
}
}
}
变异PersonMutation允许用户创建新人员或更新现有人员。
mutation PersonMutation {
personMutation {
createPersons(
personsInput: [
{
givenName: "Vasya"
surname: "Pupkin"
born: 1990
phone: "111-222-333"
email: "vpupkin@ua.com"
address: "21, Torn Street"
affiliations: [{ since: 2000, organizationId: -4, roleId: -1 }]
relations: [{ since: 2017, kind: "friend", notes: "*!", p2Id: -1 }]
}
{
givenName: "Antony"
surname: "Fields"
born: 1995
phone: "123-122-331"
email: "afields@ua.com"
address: "30, Torn Street"
affiliations: [{ since: 2015, organizationId: -3, roleId: -1 }]
relations: [
{ since: 2017, kind: "friend", notes: "*!", p2Id: -2 }
{ since: 2017, kind: "friend", notes: "*!", p2Id: 1 }
]
}
]
) {
status
message
}
}
}
集成测试位于项目ServicesTest(目录.\Tests)中。内存服务用于集成测试。这种方法大大减少了开发集成测试的工作。由于测试可以创建并最初填充数据库,因此它们可以开箱即用地运行。
结论这项工作讨论了GraphQL技术在具有事务性数据存储库的CRUD操作中的用法,并介绍了在.NET 5 C#中开发的适当服务。它还使用内存服务实现了一些有用的功能,例如JWT身份验证、OpenApi、可配置的日志和集成测试。
https://www.codeproject.com/Articles/5295966/NET-5-Services-with-GraphQL-Data-Access-and-JWT