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