尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

使用 Scala 与 Sangria 实现 GraphQL Mutations:从输入类型到数据写入的完整实战

发布时间:2026/9/25 5:33:02

资讯中心
01
ARTICLE

使用 Scala 与 Sangria 实现 GraphQL Mutations:从输入类型到数据写入的完整实战

使用 Scala 与 Sangria 实现 GraphQL Mutations:从输入类型到数据写入的完整实战
【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载导读本文讲解如何在 Scala Sangria Slick 构建的 GraphQL 服务中实现写操作mutation。你将学会定义input输入类型与对应的 Scala case class、通过deriveInputObjectType宏与FromInput类型类完成 JSON 到领域模型的转换并逐步实现createUser、createLink、createVote三个写接口最终在 GraphiQL 控制台中验证数据能够写入 H2 内存数据库。读完本文你将掌握 Sangria 中 mutation 从 Schema 定义到 DAO 持久化的完整调用链。从 query 到 mutation关键字背后的写语义在之前的章节中参见 2-preparing-first-query.md你已经学会了如何用 GraphQL 读取数据。现在要做的是写入数据语法几乎完全相同区别仅在于服务器如何判断客户端是想读还是想写——答案是使用mutation关键字替代query。语法上仅此而已但 Sangria 在内部为 mutation 引入了几类 Query 中没有的新概念本文会逐一展开input类型用于承载复合参数的类型定义InputObjectTypeinput在 Sangria 中的对应物FromInput类型类负责把传入的 JSON 结构转换为 case classDAO 中的写入方法通过 Slick 将数据真正持久化到数据库。目标 SchemaHowToGraphQL 公共 Mutation 定义本教程系列均以 HowToGraphQL 的公共 schema 为蓝本完整定义见仓库根目录的 meta/structure.graphql。其中Mutation类型定义了四个操作type Mutation { signinUser(email: AUTH_PROVIDER_EMAIL): SigninPayload! createUser(name: String!, authProvider: AuthProviderSignupData!): User createLink(description: String!, url: String!, postedById: ID): Link createVote(linkId: ID, userId: ID): Vote }本章实现除signinUser属于认证逻辑将在下一章 9-authentication.md 中完成之外的三个 mutation对应的input类型为input AuthProviderSignupData { email: AUTH_PROVIDER_EMAIL } input AUTH_PROVIDER_EMAIL { email: String! password: String! }createUser接收两个参数String类型的name与AuthProviderSignupData类型的authProvider并返回一个User。type 与 input 的区别此前我们一直使用type关键字那么input是什么input是一种专门用作参数的类型。它与type的关键差异在于type描述的是可查询返回的对象形状而input描述的是客户端提交给 mutation 的参数结构。因此你会在几乎所有 mutation 中频繁看到input的身影。实现 createUser 的完整路径按照下面的顺序逐步实现 mutation为输入定义 case classes为这些类定义InputObjectType定义负责所有 mutation 的ObjectType将 mutation 对象接入 Schema。第一步定义输入用的 case classes输入数据在 Scala 侧也需要对应的领域模型。在models包的package.scala中添加case class AuthProviderEmail(email: String, password: String) case class AuthProviderSignupData(email: AuthProviderEmail)注意这里不需要给AuthProviderEmail加Option包装虽然 GraphQL schema 中email字段是可空的但为简化示例直接使用非空字符串即可。第二步定义 InputObjectTypeInputObjectType之于input正如ObjectType之于type关键字——它告诉 Sangria 如何理解传入的数据。事实上同一个 case class 可以同时定义ObjectType和InputObjectType甚至可以定义多个。一个典型的例子是User实体注册新用户与登录时需要的字段不同你可以为这两种场景分别创建不同的InputObjectType。在GraphQLSchema.scala中添加如下定义implicit val AuthProviderEmailInputType: InputObjectType[AuthProviderEmail] deriveInputObjectTypeAuthProviderEmail ) lazy val AuthProviderSignupDataInputType: InputObjectType[AuthProviderSignupData] deriveInputObjectType[AuthProviderSignupData]()这里有两个值得注意的细节lazy关键字为了避免像上一章7-relations.md中那样遇到类型循环依赖建议对每个类型都使用lazy。UserType与LinkType相互引用时正是通过lazy val解决的implicit关键字AuthProviderEmail是AuthProviderSignupData中的嵌套对象而后者由宏deriveInputObjectType构建。为了让宏在展开时能找到嵌套类型必须让AuthProviderEmailInputType以implicit形式存在于作用域中这就是为什么它不能是lazy的原因——它必须在宏执行的那一刻即可解析。deriveInputObjectType是sangria.macros.derive包提供的宏可基于 case class 自动推导InputObjectType的字段与类型极大减少样板代码。第三步定义 Mutation 对象Mutation 对象的定义方式与 Query 类型类似使用ObjectTypeval NameArg Argument(name, StringType) val AuthProviderArg Argument(authProvider, AuthProviderSignupDataInputType) val Mutation ObjectType( Mutation, fieldsMyContext, Unit, c.arg(AuthProviderArg)) ) ) )解析要点Argument(name, StringType)声明了一个名为name、类型为标量String的参数AuthProviderArg则将authProvider参数绑定到上一步定义的输入类型Field的第一个参数是字段名createUser第二个参数UserType是返回类型arguments声明参数列表resolve是解析函数解析函数通过c.arg(NameArg)取出参数值调用c.ctx.dao.createUser(...)——c.ctx即MyContext其中封装了DAO这与此前 query 的 resolver 模式完全一致。第四步DAO 中的 createUser现在DAO还缺少createUser函数需要补充//add to imports import com.howtographql.scala.sangria.models.AuthProviderSignupData //add in body def createUser(name: String, authProvider: AuthProviderSignupData): Future[User] { val newUser User(0, name, authProvider.email.email, authProvider.email.password) val insertAndReturnUserQuery (Users returning Users.map(_.id)) into { (user, id) user.copy(id id) } db.run { insertAndReturnUserQuery newUser } }这段代码展示了 Slick 的插入并返回自增主键惯用法User(0, ...)中的id 0是占位值真正的 id 由数据库自增生成(Users returning Users.map(_.id)) into { (user, id) user.copy(id id) }表示插入后把数据库生成的id回填到 case class 中db.run(...)提交整个数据库动作返回Future[User]。第五步把 Mutation 接入 Schema在GraphQLSchema.scala中替换schemaDefinitionval SchemaDefinition Schema(QueryType, Some(Mutation))注意Some包装所有 mutation 都是可选的Sangria 要求用Option显式声明其存在。如果你此时尝试运行服务器会得到关于未实现FromInput的错误——这是下一步要解决的问题。第六步为输入类提供 FromInputSangria 需要读取 JSON 形式的结构并转换为 case class这正是FromInput类型类的作用。实现它有两种途径手写自己的 mapper使用任意 JSON 库辅助完成转换。项目在第一步1-getting-started.md中已引入sangria-spray-json依赖Sangria 会借助该库把 JSON 自动转换为合适的FromInput类型。因此我们只需为 case class 定义相应的 JSONReader并导入转换函数即可。在GraphQLSchema文件中、InputObjectType定义之前添加import sangria.marshalling.sprayJson._ import spray.json.DefaultJsonProtocol._ implicit val authProviderEmailFormat jsonFormat2(AuthProviderEmail) implicit val authProviderSignupDataFormat jsonFormat1(AuthProviderSignupData)jsonFormatN是 spray-json 根据 case class 字段数量生成JsonFormat的辅助函数implicit修饰确保 Sangria 能在隐式解析时找到它们。完成之后一切应当如预期工作。验证在 GraphiQL 控制台执行 createUser在 GraphiQL 控制台http://localhost:8080/graphiql通过sbt run启动服务器执行mutation addMe { createUser( name: Mario, authProvider:{ email:{ email:marioexample.com, password:p4ssw0rd } }){ id name } }当然你可以换用任意测试数据。如果一切正常就可以继续实现另外两个 mutation 了。实现 createLink纯标量参数的 mutationcreateLink的目标 schema 定义为createLink(description: String!, url: String!, postedById: ID): Link这里有一个提示可以跳过创建 case class 的阶段因为三个参数description、url、postedById都是String与Int这类开箱即用的简单标量不需要自定义输入类型。建议先自行尝试再对照下面的解法。DAO.createLink在DAO中添加函数def createLink(url: String, description: String, postedBy: Int): Future[Link] { val insertAndReturnLinkQuery (Links returning Links.map(_.id)) into { (link, id) link.copy(id id) } db.run { insertAndReturnLinkQuery Link(0, url, description, postedBy) } }模式与createUser完全一致Links returning Links.map(_.id)回填自增 idLink(0, ...)的postedBy字段是上一章7-relations.md中加入的用户外键。Mutation 中的 createLink 字段在GraphQLSchema文件的Mutation定义内追加字段Field(createLink, LinkType, arguments UrlArg :: DescArg :: PostedByArg :: Nil, resolve c c.ctx.dao.createLink(c.arg(UrlArg), c.arg(DescArg), c.arg(PostedByArg)))并在Mutation定义之前补充参数定义val UrlArg Argument(url, StringType) val DescArg Argument(description, StringType) val PostedByArg Argument(postedById, IntType)postedById虽在公共 schema 中声明为ID但在 Slick 的表映射里对应整型外键因此这里使用IntType。验证 createLink现在可以执行如下查询mutation addLink { createLink( url: howtographql.com, description: Great tutorial page, postedById: 1 ){ url description postedBy{ name } } }得益于上一章实现的postedBy关系fetcher deferred resolver返回结果中可以直接嵌套查询发布者User的name。实现 createVote投票的写操作最后一个 mutation 用于投票目标 schemacreateVote(linkId: ID, userId: ID): VoteDAO.createVote在DAO中添加保存新投票的函数def createVote(linkId: Int, userId: Int): Future[Vote] { val insertAndReturnVoteQuery (Votes returning Votes.map(_.id)) into { (vote, id) vote.copy(id id) } db.run { insertAndReturnVoteQuery Vote(0, userId, linkId) } }Vote(0, userId, linkId)对应 7-relations.md 中定义的Vote模型userId与linkId分别是到Users和Links表的外键。参数定义与 mutation 字段添加参数定义val LinkIdArg Argument(linkId, IntType) val UserIdArg Argument(userId, IntType)在MutationobjectType 中再添加一个字段Field(createVote, VoteType, arguments LinkIdArg :: UserIdArg :: Nil, resolve c c.ctx.dao.createVote(c.arg(LinkIdArg), c.arg(UserIdArg)))至此三个 mutation 全部完成可以在 GraphiQL 控制台中逐一测试了。本章文件变更清单本章涉及的文件最终状态如下对应源码文件在 giter8 模板项目marioosh/howtographql-scala-sangria.g8中结构可参见 1-getting-started.md 中的项目树models/package.scala新增AuthProviderEmail、AuthProviderSignupData两个输入 case classDAO.scala新增createUser、createLink、createVote三个写入函数GraphQLSchema.scala新增两个InputObjectType、FromInput支持、Mutation对象及其三个字段并将SchemaDefinition更新为Schema(QueryType, Some(Mutation))。小结与下一步现在你已经掌握了如何通过 mutation 向服务器发送数据定义输入 case class → 用宏推导InputObjectType→ 提供FromInput完成 JSON 转换 → 在Mutation对象中声明Field与Argument→ 在 DAO 中用 Slick 完成带主键回填的插入。这套模式对后续任何写接口都适用。下一章 9-authentication.md 将实现登录signinUser与鉴权逻辑届时会用FieldTag与 Sangria Middleware 保护createLink等写操作你在这里学到的 mutation 知识将直接派上用场。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐Sangria: Scala GraphQL 实现Sangria: Scala GraphQL 实现 项目基础介绍和主要编程语言 Sangria 是一个基于 Scala 编程语言的 GraphQL 实现。它旨在性能优化指南如何为unicorn-worker-killer设置最优内存和请求阈值性能优化指南如何为unicorn worker killer设置最优内存和请求阈值 unicorn worker killer 是一个专为Unicorn服务器无人机日志分析终极指南5分钟掌握免费在线工具UAV Log Viewer无人机日志分析终极指南5分钟掌握免费在线工具UAV Log Viewer 你是否曾经面对复杂的无人机飞行日志文件感到无从下手每次飞行后生成的数据文件包含了滚上一篇【实测免费】V编程语言让C代码秒变安全编译速度提升10倍的新选择下一篇Morphic后端API版本控制兼容性策略与路由设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。