ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

GraphQL Scala 实战:用 Sangria + Akka HTTP 搭建服务端并跑通第一个查询

GraphQL Scala 实战:用 Sangria + Akka HTTP 搭建服务端并跑通第一个查询 【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载本篇指南聚焦于 HowToGraphQL 的 Scala 后端教程中「准备第一个查询」这一核心章节从定义Link领域模型、打通 Slick H2 数据库存取到实现 GraphQL 服务层、定义 Schema最终在 GraphiQL 控制台中执行并验证第一条allLinks查询。读完本篇你将掌握 Sangria 服务端完整的执行栈Context → GraphQLServer → Schema → DAO并能独立在本地跑通一个可查询数据库数据的 GraphQL 端点。本章目标运行第一条 GraphQL 查询本教程的最终目标是在浏览器中通过 GraphiQL 控制台执行下面这条查询并从内存数据库 H2 中取回链接列表query { allLinks { id url description } }期望的响应是一个从数据库读取出来的链接列表。要达成这个目标需要依次完成以下四件事定义Link领域模型让数据库支持该模型并预置测试数据实现完整的 GraphQL 执行栈Context、GraphQLServer、Schema、DAO配置 HTTP 路由并暴露 GraphiQL 控制台。在开始之前请确认你已经按照教程第一章完成了项目初始化通过 giter8 模板sbt new marioosh/howtographql-scala-sangria.g8生成项目项目结构包含Server.scalaHTTP 入口、DAO.scala数据库访问、DBSchema.scala数据库 Schema以及src/main/resources/application.confH2 内存数据库配置和graphiql.html文件。相关前置内容可参考 1-getting-started.md 与 0-introduction.md。定义领域模型models 包与 Link case class在添加第一个模型之前需要先决定所有领域模型存放的位置。教程推荐的做法是创建一个独立的models包把全部领域模型集中放在一个文件中这样便于查找、便于在教程推进中对比版本差异也便于识别哪些代码需要变更。首先创建models包及其package.scalapackage com.howtographql.scala.sangria package object models { }从这一刻起所有与领域相关的模型都会放进这个文件。接下来定义第一个最基本的模型Link在package.scala中补充 case classcase class Link(id: Int, url: String, description: String)可以看到这个Link模型的字段比第一章展示的目标 Schema 中的Link少很多目标 Schema 中Link还包含createdAt、postedBy、votes等字段可参考 meta/structure.graphql 以及 0-introduction.md。教程刻意保持模型简单因为当前阶段的重心是跑通完整的执行栈而不是让模型一步到位——后续章节如关系、认证会逐步为Link增加字段。让数据库支持 LinkSlick 表映射与初始化数据现在要让简单数据库支持这个模型并预置一些数据。需要修改DBSchema.scala一共三处改动//in the imports section: import com.howtographql.scala.sangria.models._ //In the object body: //1 class LinksTable(tag: Tag) extends TableLink{ def id columnInt def url columnString def description columnString def * (id, url, description).mapTo[Link] } //2 val Links TableQuery[LinksTable] //3 val databaseSetup DBIO.seq( Links.schema.create, Links forceInsertAll Seq( Link(1, http://howtographql.com, Awesome community driven GraphQL tutorial), Link(2, http://graphql.org, Official GraphQL web page), Link(3, https://graphql.org/, GraphQL specification) ) )这三处改动分别承担不同的职责1 — 表映射定义LinksTable继承 Slick 的Table[Link]将 Scala 中的Link映射到数据库表LINKS。id列声明为O.PrimaryKey, O.AutoInc主键且自增url与description为普通字符串列def *是 Slick 的默认投影通过.mapTo[Link]将(id, url, description)三元组直接映射为Link对象。2 — 表查询助手val Links TableQuery[LinksTable]提供一个用于访问该表数据的 helper后续 DAO 中的Links.filter(...)、Links.result等操作都依赖它。3 — 建表与初始化数据databaseSetup负责创建表结构Links.schema.create并通过Links forceInsertAll一次性插入三条预置数据。注意这里需要用这段代码替换模板中提供的空函数否则建表与数据初始化不会执行。关于数据库本身项目选用的是 H2 内存数据库在src/main/resources/application.conf中配置h2mem { url jdbc:h2:mem:howtographqldb driver org.h2.Driver connectionPool disabled keepAliveConnection true }H2 可以完全在内存中运行、无需在操作系统安装额外组件非常适合本教程场景。DBSchema.createDatabase会基于该配置创建数据库连接供后续GraphQLServer使用。提供 GraphQL 上下文MyContextContext 是一个贯穿整个查询执行过程的对象大多数情况下它保持不变。它的核心职责是提供完成查询所需的数据与工具类。在本例中把DAO放进 Context使所有查询解析器都能访问数据库未来认证数据也会放进这里。因为 Context 与领域模型没有直接关系教程建议把它放在sangria包中与其他文件并列命名为MyContextpackage com.howtographql.scala.sangria case class MyContext(dao: DAO) { }目前这个文件还没有实际逻辑但创建它是为了让服务器能够跑起来——它是Executor.execute的第三个参数后面会看到。后续章节如认证会为MyContext增加currentUser、login、ensureAuthenticated等方法可参考 9-authentication.md。实现 GraphQL 服务层GraphQLServer现在实现整个后端中最重要的文件之一——GraphQLServer。它在架构中位于第二层紧跟在 HTTP 服务器之后HTTP 请求被转换为 JSON 对象送入该层GraphQLServer将 JSON 解析为 GraphQL 查询并执行再通过 HTTP 层把响应返回客户端同时负责捕获 GraphQL 解析错误并转换为合适的 HTTP 响应。创建GraphQLServer.scalapackage com.howtographql.scala.sangria import akka.http.scaladsl.server.Route import sangria.parser.QueryParser import spray.json.{JsObject, JsString, JsValue} import akka.http.scaladsl.model.StatusCodes._ import akka.http.scaladsl.server.Directives._ import scala.concurrent.ExecutionContext import scala.util.{Failure, Success} import akka.http.scaladsl.server._ import sangria.ast.Document import sangria.execution._ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import sangria.marshalling.sprayJson._ object GraphQLServer { // 1 private val dao DBSchema.createDatabase // 2 def endpoint(requestJSON: JsValue)(implicit ec: ExecutionContext): Route { // 3 val JsObject(fields) requestJSON // 4 val JsString(query) fields(query) // 5 QueryParser.parse(query) match { case Success(queryAst) // 6 val operation fields.get(operationName) collect { case JsString(op) op } // 7 val variables fields.get(variables) match { case Some(obj: JsObject) obj case _ JsObject.empty } // 8 complete(executeGraphQLQuery(queryAst, operation, variables)) case Failure(error) complete(BadRequest, JsObject(error - JsString(error.getMessage))) } } private def executeGraphQLQuery(query: Document, operation: Option[String], vars: JsObject)(implicit ec: ExecutionContext) { // 9 Executor.execute( GraphQLSchema.SchemaDefinition, // 10 query, // 11 MyContext(dao), // 12 variables vars, // 13 operationName operation // 14 ).map(OK - _) .recover { case error: QueryAnalysisError BadRequest - error.resolveError case error: ErrorWithResolver InternalServerError - error.resolveError } } }逐点分析这个文件的执行流程数据库连接private val dao DBSchema.createDatabase创建数据库访问对象供整个服务使用。endpoint 入口endpoint返回Route类型会直接被 HTTP 服务器的路由使用它期望接收一个 JSON 对象参数。解析根 JSON 对象从根对象中提取主 JSON 对象其结构包含三个子对象{ query: {}, variables: {}, operationName: }query是查询本身variables是查询的附加数据——GraphQL 允许把查询与参数分开传输operationName用于给查询命名可以把查询理解成一个函数通常使用匿名函数但出于日志等目的也可以给它起名这个名称就通过operationName传递。提取 query通过模式匹配val JsString(query) fields(query)取出查询字符串。解析查询拿到查询后必须解析Sangria 提供了QueryParser.parse。解析失败时服务器返回 400 状态码并在响应体中携带错误描述解析成功后继续提取另外两个键。提取 operationNamefields.get(operationName)通过collect取出字符串值Option[String]。提取 variablesfields.get(variables)若存在且为JsObject则直接使用否则回退为空对象JsObject.empty。执行三个对象queryAst、operation、variables被一并传给执行函数。Executor.execute这是该类中最关键的一次调用——查询真正在此执行。成功时结果直接返回客户端失败时服务器返回 4xx 状态码并附上错误说明。Executor 需要的数据包括来自请求的query11、operationName14、variables13以及GraphQLSchema.SchemaDefinition与MyContext(dao)。SchemaDefinition包含我们的 Schema——客户端能查询什么、数据如何被取回、使用哪个数据源数据库、其他服务的 REST 调用等。简言之SchemaDefinition定义了我们要暴露的内容从 GraphQL 视角看它定义了类型与客户端可查询的 Schema 形状。该文件尚未创建将在下一步完成。MyContext即上一节创建的上下文对象携带DAO供解析器访问数据库。定义 GraphQLSchemaObjectType、字段与 resolveSangria 无法直接复用领域中的 case class它需要自己的ObjectType对象。这反而带来了好处API/Sangria 模型与数据库表示解耦可以自由地隐藏、添加或聚合字段。创建GraphQLSchema对象package com.howtographql.scala.sangria import sangria.schema.{Field, ListType, ObjectType} import models._ // # import sangria.schema._ import sangria.macros.derive._ object GraphQLSchema { // 1 val LinkType ObjectTypeUnit, Link, Field(url, StringType, resolve _.value.url), Field(description, StringType, resolve _.value.description) ) ) // 2 val QueryType ObjectType( Query, fieldsMyContext, Unit, resolve c c.ctx.dao.allLinks) ) ) // 3 val SchemaDefinition Schema(QueryType) }1 — LinkTypeLink类的ObjectType定义。第一个字符串参数Link定义 Schema 中的类型名可以不同于 case class 的名字fields中需要定义所有想要暴露的字段/函数每个字段都必须包含一个resolve函数告诉 Sangria 如何为该字段取数据同时还要显式声明字段类型。2 — QueryTypeSchema 的顶层对象名为Query。它连同嵌套对象会显示在稍后集成的 GraphiQL 控制台中。目前fields只定义了一个字段Field(allLinks, ListType(LinkType), resolve c c.ctx.dao.allLinks)这定义了一个名为allLinks的 GraphQL 字段其类型是Link的列表ListType(LinkType)。解析器需要调用DAO对象中的allLinks函数所以下一步必须实现它。3 — SchemaDefinition通过Schema(QueryType)组装最终的可执行 Schema。用宏消除样板代码当需要映射大量 case class 时手写字段定义会比较繁琐。Sangria 提供了派生宏来避免样板代码implicit val LinkType deriveObjectType[Unit, Link]()它与上面手写的定义效果完全一致。使用宏方式时不要忘记导入sangria.macros.derive._。在 DAO 中实现 allLinks 数据访问按照 Schema 中resolve的约定需要在DAO对象中实现allLinks函数。当前DAO.scala的完整状态如下package com.howtographql.scala.sangria import DBSchema._ import slick.jdbc.H2Profile.api._ class DAO(db: Database) { def allLinks db.run(Links.result) }allLinks通过db.run(Links.result)把 Slick 的查询动作提交到数据库执行——Links即上一节中DBSchema里定义的TableQueryLinks.result返回表内全部记录。教程后续章节会在此基础上扩展参数化查询如按 id 取单个链接、按 id 列表取多个链接可参考 3-handling-arguments.md。集成 GraphiQL 控制台GraphiQL 让我们能在浏览器中直接对服务器执行查询是调试 GraphQL 服务非常方便的交互式控制台。giter8 模板已经包含了对应文件位于src/main/resources/graphiql.html。接下来要做的就是配置 HTTP 服务器让这个文件被暴露、可在浏览器中访问。配置 HTTP 服务器端点完成本章目标的最后一步是配置 HTTP 服务器暴露graphiql.html文件并开放一个接收 GraphQL 查询的端点。打开Server.scala用下面的路由替换原来的route函数val route: Route (post path(graphql)) { entity(as[JsValue]) { requestJson GraphQLServer.endpoint(requestJson) } } ~ { getFromResource(graphiql.html) }新的route定义只有两个端点POST /graphql所有 POST 到/graphql的请求都被委托给GraphQLServer.endpoint。请求体通过entity(as[JsValue])反序列化为 JSON这里使用 Spray JSON 库完成 marshalling/unmarshalling再交给 GraphQL 服务层处理。其余所有请求统一返回graphiql.html文件内容getFromResource从 classpath 资源中读取。运行服务器并执行第一个查询一切就绪启动服务器sbt run然后在浏览器中打开地址 http://localhost:8080/graphiql。如果在项目初始化时选择了非默认端口请使用你配置的端口号。开发过程中也可以借助第一章提到的RevolverSBT 插件它在保存文件时自动重启服务器让 Akka HTTP 始终服务最新版本。在 GraphiQL 控制台中执行以下查询query { allLinks { id url description } }期望的响应如下{ data: { allLinks: [ { id: 1, url: http://howtographql.com, description: Awesome community driven GraphQL tutorial }, { id: 2, url: http://graphql.org, description: Official GraphQL web page }, { id: 3, url: https://graphql.org/, description: GraphQL specification } ] } }响应中的三条数据正是DBSchema.scala里databaseSetup预置的三条链接记录——它们经过「HTTP 层 → GraphQLServer 解析 → Executor 执行 → DAO 查询 H2 内存库」的完整链路后被返回。小结与后续方向至此整个 GraphQL 服务器执行栈已经配置完毕并成功跑通了第一条基础查询领域层models包中的Linkcase class 定义了领域模型数据层DBSchema完成 Slick 表映射与初始化数据DAO.allLinks负责数据库访问执行层MyContext提供查询上下文GraphQLServer完成 JSON 解析、查询执行与错误处理Schema 层GraphQLSchema定义了LinkType、QueryType与SchemaDefinition接入层Server的 HTTP 路由同时承载/graphql端点与 GraphiQL 控制台。后续章节将在此基础之上继续演进为查询增加参数见 3-handling-arguments.md、用 Deferred Resolver 与 Fetcher 优化重复查询见 4-deferred-resolvers.md、定义自定义标量与接口、建立模型关系以及实现认证等。整个教程的目标 Schema含User、Vote、Mutation、Subscription等类型可在 meta/structure.graphql 中查看。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐高效音频解密工具qmc-decoder3分钟解锁QQ音乐加密文件实现跨平台播放高效音频解密工具qmc decoder3分钟解锁QQ音乐加密文件实现跨平台播放 在数字音乐时代你是否曾因QQ音乐加密格式的限制而无法在其他设备上播放心爱的歌CANN/asc-devkit浮点转整型APIasc_float2int16 产品支持情况 | 产品 | 是否支持 | | : | : :| | term Atlas A3 训练系列产品/Atlas A3Sangria: Scala GraphQL 实现Sangria: Scala GraphQL 实现 项目基础介绍和主要编程语言 Sangria 是一个基于 Scala 编程语言的 GraphQL 实现。它旨在上一篇终极指南TranslucentTB如何实现Windows任务栏透明化效果下一篇CefFlashBrowser突破Flash限制的终极浏览器解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表