現(xiàn) GraphQL Mutations:從輸入類型到數(shù)據(jù)寫入的完整實(shí)戰(zhàn))
【免費(fèi)下載鏈接】howtographqlThe Fullstack Tutorial for GraphQL項(xiàng)目地址https://gitcode.com/gh_mirrors/ho/howtographql點(diǎn)擊查看免費(fèi)下載導(dǎo)讀本文講解如何在 Scala Sangria Slick 構(gòu)建的 GraphQL 服務(wù)中實(shí)現(xiàn)寫操作mutation。你將學(xué)會(huì)定義input輸入類型與對應(yīng)的 Scala case class、通過deriveInputObjectType宏與FromInput類型類完成 JSON 到領(lǐng)域模型的轉(zhuǎn)換并逐步實(shí)現(xiàn)createUser、createLink、createVote三個(gè)寫接口最終在 GraphiQL 控制臺(tái)中驗(yàn)證數(shù)據(jù)能夠?qū)懭?H2 內(nèi)存數(shù)據(jù)庫。讀完本文你將掌握 Sangria 中 mutation 從 Schema 定義到 DAO 持久化的完整調(diào)用鏈。從 query 到 mutation關(guān)鍵字背后的寫語義在之前的章節(jié)中參見 2-preparing-first-query.md你已經(jīng)學(xué)會(huì)了如何用 GraphQL 讀取數(shù)據(jù)?,F(xiàn)在要做的是寫入數(shù)據(jù)語法幾乎完全相同區(qū)別僅在于服務(wù)器如何判斷客戶端是想讀還是想寫——答案是使用mutation關(guān)鍵字替代query。語法上僅此而已但 Sangria 在內(nèi)部為 mutation 引入了幾類 Query 中沒有的新概念本文會(huì)逐一展開input類型用于承載復(fù)合參數(shù)的類型定義InputObjectTypeinput在 Sangria 中的對應(yīng)物FromInput類型類負(fù)責(zé)把傳入的 JSON 結(jié)構(gòu)轉(zhuǎn)換為 case classDAO 中的寫入方法通過 Slick 將數(shù)據(jù)真正持久化到數(shù)據(jù)庫。目標(biāo) SchemaHowToGraphQL 公共 Mutation 定義本教程系列均以 HowToGraphQL 的公共 schema 為藍(lán)本完整定義見倉庫根目錄的 meta/structure.graphql。其中Mutation類型定義了四個(gè)操作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 }本章實(shí)現(xiàn)除signinUser屬于認(rèn)證邏輯將在下一章 9-authentication.md 中完成之外的三個(gè) mutation對應(yīng)的input類型為input AuthProviderSignupData { email: AUTH_PROVIDER_EMAIL } input AUTH_PROVIDER_EMAIL { email: String! password: String! }createUser接收兩個(gè)參數(shù)String類型的name與AuthProviderSignupData類型的authProvider并返回一個(gè)User。type 與 input 的區(qū)別此前我們一直使用type關(guān)鍵字那么input是什么input是一種專門用作參數(shù)的類型。它與type的關(guān)鍵差異在于type描述的是可查詢返回的對象形狀而input描述的是客戶端提交給 mutation 的參數(shù)結(jié)構(gòu)。因此你會(huì)在幾乎所有 mutation 中頻繁看到input的身影。實(shí)現(xiàn) createUser 的完整路徑按照下面的順序逐步實(shí)現(xiàn) mutation為輸入定義 case classes為這些類定義InputObjectType定義負(fù)責(zé)所有 mutation 的ObjectType將 mutation 對象接入 Schema。第一步定義輸入用的 case classes輸入數(shù)據(jù)在 Scala 側(cè)也需要對應(yīng)的領(lǐng)域模型。在models包的package.scala中添加case class AuthProviderEmail(email: String, password: String) case class AuthProviderSignupData(email: AuthProviderEmail)注意這里不需要給AuthProviderEmail加Option包裝雖然 GraphQL schema 中email字段是可空的但為簡化示例直接使用非空字符串即可。第二步定義 InputObjectTypeInputObjectType之于input正如ObjectType之于type關(guān)鍵字——它告訴 Sangria 如何理解傳入的數(shù)據(jù)。事實(shí)上同一個(gè) case class 可以同時(shí)定義ObjectType和InputObjectType甚至可以定義多個(gè)。一個(gè)典型的例子是User實(shí)體注冊新用戶與登錄時(shí)需要的字段不同你可以為這兩種場景分別創(chuàng)建不同的InputObjectType。在GraphQLSchema.scala中添加如下定義implicit val AuthProviderEmailInputType: InputObjectType[AuthProviderEmail] deriveInputObjectTypeAuthProviderEmail ) lazy val AuthProviderSignupDataInputType: InputObjectType[AuthProviderSignupData] deriveInputObjectType[AuthProviderSignupData]()這里有兩個(gè)值得注意的細(xì)節(jié)lazy關(guān)鍵字為了避免像上一章7-relations.md中那樣遇到類型循環(huán)依賴建議對每個(gè)類型都使用lazy。UserType與LinkType相互引用時(shí)正是通過lazy val解決的implicit關(guān)鍵字AuthProviderEmail是AuthProviderSignupData中的嵌套對象而后者由宏deriveInputObjectType構(gòu)建。為了讓宏在展開時(shí)能找到嵌套類型必須讓AuthProviderEmailInputType以implicit形式存在于作用域中這就是為什么它不能是lazy的原因——它必須在宏執(zhí)行的那一刻即可解析。deriveInputObjectType是sangria.macros.derive包提供的宏可基于 case class 自動(dòng)推導(dǎo)InputObjectType的字段與類型極大減少樣板代碼。第三步定義 Mutation 對象Mutation 對象的定義方式與 Query 類型類似使用ObjectTypeval NameArg Argument(name, StringType) val AuthProviderArg Argument(authProvider, AuthProviderSignupDataInputType) val Mutation ObjectType( Mutation, fieldsMyContext, Unit, c.arg(AuthProviderArg)) ) ) )解析要點(diǎn)Argument(name, StringType)聲明了一個(gè)名為name、類型為標(biāo)量String的參數(shù)AuthProviderArg則將authProvider參數(shù)綁定到上一步定義的輸入類型Field的第一個(gè)參數(shù)是字段名createUser第二個(gè)參數(shù)UserType是返回類型arguments聲明參數(shù)列表resolve是解析函數(shù)解析函數(shù)通過c.arg(NameArg)取出參數(shù)值調(diào)用c.ctx.dao.createUser(...)——c.ctx即MyContext其中封裝了DAO這與此前 query 的 resolver 模式完全一致。第四步DAO 中的 createUser現(xiàn)在DAO還缺少createUser函數(shù)需要補(bǔ)充//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 由數(shù)據(jù)庫自增生成(Users returning Users.map(_.id)) into { (user, id) user.copy(id id) }表示插入后把數(shù)據(jù)庫生成的id回填到 case class 中db.run(...)提交整個(gè)數(shù)據(jù)庫動(dòng)作返回Future[User]。第五步把 Mutation 接入 Schema在GraphQLSchema.scala中替換schemaDefinitionval SchemaDefinition Schema(QueryType, Some(Mutation))注意Some包裝所有 mutation 都是可選的Sangria 要求用Option顯式聲明其存在。如果你此時(shí)嘗試運(yùn)行服務(wù)器會(huì)得到關(guān)于未實(shí)現(xiàn)FromInput的錯(cuò)誤——這是下一步要解決的問題。第六步為輸入類提供 FromInputSangria 需要讀取 JSON 形式的結(jié)構(gòu)并轉(zhuǎn)換為 case class這正是FromInput類型類的作用。實(shí)現(xiàn)它有兩種途徑手寫自己的 mapper使用任意 JSON 庫輔助完成轉(zhuǎn)換。項(xiàng)目在第一步1-getting-started.md中已引入sangria-spray-json依賴Sangria 會(huì)借助該庫把 JSON 自動(dòng)轉(zhuǎn)換為合適的FromInput類型。因此我們只需為 case class 定義相應(yīng)的 JSONReader并導(dǎo)入轉(zhuǎn)換函數(shù)即可。在GraphQLSchema文件中、InputObjectType定義之前添加import sangria.marshalling.sprayJson._ import spray.json.DefaultJsonProtocol._ implicit val authProviderEmailFormat jsonFormat2(AuthProviderEmail) implicit val authProviderSignupDataFormat jsonFormat1(AuthProviderSignupData)jsonFormatN是 spray-json 根據(jù) case class 字段數(shù)量生成JsonFormat的輔助函數(shù)implicit修飾確保 Sangria 能在隱式解析時(shí)找到它們。完成之后一切應(yīng)當(dāng)如預(yù)期工作。驗(yàn)證在 GraphiQL 控制臺(tái)執(zhí)行 createUser在 GraphiQL 控制臺(tái)http://localhost:8080/graphiql通過sbt run啟動(dòng)服務(wù)器執(zhí)行mutation addMe { createUser( name: Mario, authProvider:{ email:{ email:marioexample.com, password:p4ssw0rd } }){ id name } }當(dāng)然你可以換用任意測試數(shù)據(jù)。如果一切正常就可以繼續(xù)實(shí)現(xiàn)另外兩個(gè) mutation 了。實(shí)現(xiàn) createLink純標(biāo)量參數(shù)的 mutationcreateLink的目標(biāo) schema 定義為createLink(description: String!, url: String!, postedById: ID): Link這里有一個(gè)提示可以跳過創(chuàng)建 case class 的階段因?yàn)槿齻€(gè)參數(shù)description、url、postedById都是String與Int這類開箱即用的簡單標(biāo)量不需要自定義輸入類型。建議先自行嘗試再對照下面的解法。DAO.createLink在DAO中添加函數(shù)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定義內(nèi)追加字段Field(createLink, LinkType, arguments UrlArg :: DescArg :: PostedByArg :: Nil, resolve c c.ctx.dao.createLink(c.arg(UrlArg), c.arg(DescArg), c.arg(PostedByArg)))并在Mutation定義之前補(bǔ)充參數(shù)定義val UrlArg Argument(url, StringType) val DescArg Argument(description, StringType) val PostedByArg Argument(postedById, IntType)postedById雖在公共 schema 中聲明為ID但在 Slick 的表映射里對應(yīng)整型外鍵因此這里使用IntType。驗(yàn)證 createLink現(xiàn)在可以執(zhí)行如下查詢mutation addLink { createLink( url: howtographql.com, description: Great tutorial page, postedById: 1 ){ url description postedBy{ name } } }得益于上一章實(shí)現(xiàn)的postedBy關(guān)系fetcher deferred resolver返回結(jié)果中可以直接嵌套查詢發(fā)布者User的name。實(shí)現(xiàn) createVote投票的寫操作最后一個(gè) mutation 用于投票目標(biāo) schemacreateVote(linkId: ID, userId: ID): VoteDAO.createVote在DAO中添加保存新投票的函數(shù)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)對應(yīng) 7-relations.md 中定義的Vote模型userId與linkId分別是到Users和Links表的外鍵。參數(shù)定義與 mutation 字段添加參數(shù)定義val LinkIdArg Argument(linkId, IntType) val UserIdArg Argument(userId, IntType)在MutationobjectType 中再添加一個(gè)字段Field(createVote, VoteType, arguments LinkIdArg :: UserIdArg :: Nil, resolve c c.ctx.dao.createVote(c.arg(LinkIdArg), c.arg(UserIdArg)))至此三個(gè) mutation 全部完成可以在 GraphiQL 控制臺(tái)中逐一測試了。本章文件變更清單本章涉及的文件最終狀態(tài)如下對應(yīng)源碼文件在 giter8 模板項(xiàng)目marioosh/howtographql-scala-sangria.g8中結(jié)構(gòu)可參見 1-getting-started.md 中的項(xiàng)目樹models/package.scala新增AuthProviderEmail、AuthProviderSignupData兩個(gè)輸入 case classDAO.scala新增createUser、createLink、createVote三個(gè)寫入函數(shù)GraphQLSchema.scala新增兩個(gè)InputObjectType、FromInput支持、Mutation對象及其三個(gè)字段并將SchemaDefinition更新為Schema(QueryType, Some(Mutation))。小結(jié)與下一步現(xiàn)在你已經(jīng)掌握了如何通過 mutation 向服務(wù)器發(fā)送數(shù)據(jù)定義輸入 case class → 用宏推導(dǎo)InputObjectType→ 提供FromInput完成 JSON 轉(zhuǎn)換 → 在Mutation對象中聲明Field與Argument→ 在 DAO 中用 Slick 完成帶主鍵回填的插入。這套模式對后續(xù)任何寫接口都適用。下一章 9-authentication.md 將實(shí)現(xiàn)登錄signinUser與鑒權(quán)邏輯屆時(shí)會(huì)用FieldTag與 Sangria Middleware 保護(hù)createLink等寫操作你在這里學(xué)到的 mutation 知識(shí)將直接派上用場。贊分享【免費(fèi)下載鏈接】howtographqlThe Fullstack Tutorial for GraphQL項(xiàng)目地址https://gitcode.com/gh_mirrors/ho/howtographql點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Sangria: Scala GraphQL 實(shí)現(xiàn)Sangria: Scala GraphQL 實(shí)現(xiàn) 項(xiàng)目基礎(chǔ)介紹和主要編程語言 Sangria 是一個(gè)基于 Scala 編程語言的 GraphQL 實(shí)現(xiàn)。它旨在性能優(yōu)化指南如何為unicorn-worker-killer設(shè)置最優(yōu)內(nèi)存和請求閾值性能優(yōu)化指南如何為unicorn worker killer設(shè)置最優(yōu)內(nèi)存和請求閾值 unicorn worker killer 是一個(gè)專為Unicorn服務(wù)器無人機(jī)日志分析終極指南5分鐘掌握免費(fèi)在線工具UAV Log Viewer無人機(jī)日志分析終極指南5分鐘掌握免費(fèi)在線工具UAV Log Viewer 你是否曾經(jīng)面對復(fù)雜的無人機(jī)飛行日志文件感到無從下手每次飛行后生成的數(shù)據(jù)文件包含了滾上一篇【實(shí)測免費(fèi)】V編程語言讓C代碼秒變安全編譯速度提升10倍的新選擇下一篇Morphic后端API版本控制兼容性策略與路由設(shè)計(jì)創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考