<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          項(xiàng)目實(shí)戰(zhàn):使用 Go 構(gòu)建 GraphQL API

          共 12125字,需瀏覽 25分鐘

           ·

          2021-01-19 08:59

          點(diǎn)擊上方藍(lán)色“Go語(yǔ)言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

          2020/5/16 更新:大家好,我剛剛更新了該項(xiàng)目以使用 Go module。不幸的是,realize[1]很長(zhǎng)時(shí)間沒(méi)有更新并且無(wú)法正常工作。如果您想使用實(shí)時(shí)重新加載器,則還有其他選擇,例如 air[2]。否則,請(qǐng)隨意忽略帖子中有關(guān) realize 的任何內(nèi)容,并按通常的方式運(yùn)行項(xiàng)目。

          本博文中將使用 GoGraphQLPostgreSQL 創(chuàng)建一個(gè) API。我已在項(xiàng)目結(jié)構(gòu)上迭代幾個(gè)版本,這是我最喜歡的一個(gè)。在大部分的時(shí)間,我創(chuàng)建 web APIs 都是通過(guò) Node.jsRuby/Rails。而第一次使用 Go 設(shè)計(jì) Web apis 時(shí),需要費(fèi)很大的勁兒。Ben JohnsonStructuring Applications in Go[3] 文章對(duì)我有很大的幫助,本博文中的部分代碼就得益于 Ben Johnson 文章的指導(dǎo),推薦閱讀。

          配置

          首先,從項(xiàng)目的配置開(kāi)始。在本篇博文中,我將在 macOS 中完成,但這并不重要。如果在你的 macOS 上還沒(méi)有 GoPostGreSQLbradford-hamilton/go-graphql-api[4] 詳細(xì)講解了如何在 macOS 上配置 GoPostgreSQL.

          創(chuàng)建一個(gè)新項(xiàng)目--go-graphal-api,整體項(xiàng)目結(jié)構(gòu)如下:

          ├──?gql
          │???├──?gql.go
          │???├──?queries.go
          │???├──?resolvers.go
          │???└──?types.go
          ├──?main.go
          ├──?postgres
          │???└──?postgres.go
          └──?server
          ?└──?server.go

          有一些額外依賴需要安裝。開(kāi)發(fā)中熱加載的 realize[5],go-chi 的輕量級(jí)路由 chi[6] 和管理 request/response 負(fù)載的 render[7],以及 graphql-go/graphql[8]

          go?get?github.com/oxequa/realize
          go?get?github.com/go-chi/chi
          go?get?github.com/go-chi/render
          go?get?github.com/graphql-go/graphql
          go?get?github.com/lib/pq

          最后,創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)和一些測(cè)試使用的數(shù)據(jù),在 Postgres 的命令行中輸入 psql,創(chuàng)建一個(gè)數(shù)據(jù)庫(kù):

          CREATE?DATABASE?go_graphql_db;

          然后連接上該庫(kù):

          \c?go_graphql_db

          連接上后,將以下 sql 語(yǔ)句粘貼到命令行:

          CREATE?TABLE?users?(
          ??id?serial?PRIMARY?KEY,
          ??name?VARCHAR?(50)?NOT?NULL,
          ??age?INT?NOT?NULL,
          ??profession?VARCHAR?(50)?NOT?NULL,
          ??friendly?BOOLEAN?NOT?NULL
          );

          INSERT?INTO?users?VALUES
          ??(1,?'kevin',?35,?'waiter',?true),
          ??(2,?'angela',?21,?'concierge',?true),
          ??(3,?'alex',?26,?'zoo?keeper',?false),
          ??(4,?'becky',?67,?'retired',?false),
          ??(5,?'kevin',?15,?'in?school',?true),
          ??(6,?'frankie',?45,?'teller',?true);

          我們創(chuàng)建了一個(gè)基礎(chǔ)的用戶表并新增了 6 條新用戶數(shù)據(jù),對(duì)本博文來(lái)說(shuō)已經(jīng)足夠。接下來(lái)開(kāi)始構(gòu)建我們的 API。

          API

          在這篇博文中,所有的代碼片段都會(huì)包含一些注釋,以幫助理解每一步。

          main.go 開(kāi)始:

          package?main

          import?(
          ?"fmt"
          ?"log"
          ?"net/http"

          ?"github.com/bradford-hamilton/go-graphql-api/gql"
          ?"github.com/bradford-hamilton/go-graphql-api/postgres"
          ?"github.com/bradford-hamilton/go-graphql-api/server"
          ?"github.com/go-chi/chi"
          ?"github.com/go-chi/chi/middleware"
          ?"github.com/go-chi/render"
          ?"github.com/graphql-go/graphql"
          )

          func?main()?{
          ?//?Initialize?our?API?and?return?a?pointer?to?our?router?for?http.ListenAndServe
          ?//?and?a?pointer?to?our?db?to?defer?its?closing?when?main()?is?finished
          ?router,?db?:=?initializeAPI()
          ?defer?db.Close()

          ?//?Listen?on?port?4000?and?if?there's?an?error?log?it?and?exit
          ?log.Fatal(http.ListenAndServe(":4000",?router))
          }

          func?initializeAPI()?(*chi.Mux,?*postgres.Db)?{
          ?//?Create?a?new?router
          ?router?:=?chi.NewRouter()

          ?//?Create?a?new?connection?to?our?pg?database
          ?db,?err?:=?postgres.New(
          ??postgres.ConnString("localhost",?5432,?"bradford",?"go_graphql_db"),
          ?)
          ?if?err?!=?nil?{
          ??log.Fatal(err)
          ?}

          ?//?Create?our?root?query?for?graphql
          ?rootQuery?:=?gql.NewRoot(db)
          ?//?Create?a?new?graphql?schema,?passing?in?the?the?root?query
          ?sc,?err?:=?graphql.NewSchema(
          ??graphql.SchemaConfig{Query:?rootQuery.Query},
          ?)
          ?if?err?!=?nil?{
          ??fmt.Println("Error?creating?schema:?",?err)
          ?}

          ?//?Create?a?server?struct?that?holds?a?pointer?to?our?database?as?well
          ?//?as?the?address?of?our?graphql?schema
          ?s?:=?server.Server{
          ??GqlSchema:?&sc,
          ?}

          ?//?Add?some?middleware?to?our?router
          ?router.Use(
          ??render.SetContentType(render.ContentTypeJSON),?//?set?content-type?headers?as?application/json
          ??middleware.Logger,??????????//?log?API?request?calls
          ??middleware.DefaultCompress,?//?compress?results,?mostly?gzipping?assets?and?json
          ??middleware.StripSlashes,????//?match?paths?with?a?trailing?slash,?strip?it,?and?continue?routing?through?the?mux
          ??middleware.Recoverer,???????//?recover?from?panics?without?crashing?server
          ?)

          ?//?Create?the?graphql?route?with?a?Server?method?to?handle?it
          ?router.Post("/graphql",?s.GraphQL())

          ?return?router,?db
          }

          在上面導(dǎo)入的 gqlpostgresserver 的路徑應(yīng)該是你本地的路徑,以及 postgres.ConnString() 中連接 PostgreSQL 的用戶名也應(yīng)該是你自己的,和我的不一樣。

          initializeAPI() 分為幾大塊主要的部分,接下來(lái)我們逐步構(gòu)建每一塊。

          使用 chi.NewRouter() 創(chuàng)建 router 并返回一個(gè) mux,接下來(lái)是創(chuàng)建一個(gè) PostgreSQL 數(shù)據(jù)庫(kù)連接。

          使用 postgres.ConnString() 創(chuàng)建一個(gè) string 類型的連接配置,并封裝到 postgres.New() 函數(shù)中。這些邏輯在我們自己包中的 postgres.go 文件中構(gòu)建:

          package?postgres

          import?(
          ?"database/sql"
          ?"fmt"

          ?//?postgres?driver
          ?_?"github.com/lib/pq"
          )

          //?Db?is?our?database?struct?used?for?interacting?with?the?database
          type?Db?struct?{
          ?*sql.DB
          }

          //?New?makes?a?new?database?using?the?connection?string?and
          //?returns?it,?otherwise?returns?the?error
          func?New(connString?string)?(*Db,?error)?{
          ?db,?err?:=?sql.Open("postgres",?connString)
          ?if?err?!=?nil?{
          ??return?nil,?err
          ?}

          ?//?Check?that?our?connection?is?good
          ?err?=?db.Ping()
          ?if?err?!=?nil?{
          ??return?nil,?err
          ?}

          ?return?&Db{db},?nil
          }

          //?ConnString?returns?a?connection?string?based?on?the?parameters?it's?given
          //?This?would?normally?also?contain?the?password,?however?we're?not?using?one
          func?ConnString(host?string,?port?int,?user?string,?dbName?string)?string?{
          ?return?fmt.Sprintf(
          ??"host=%s?port=%d?user=%s?dbname=%s?sslmode=disable",
          ??host,?port,?user,?dbName,
          ?)
          }

          //?User?shape
          type?User?struct?{
          ?ID?????????int
          ?Name???????string
          ?Age????????int
          ?Profession?string
          ?Friendly???bool
          }

          //?GetUsersByName?is?called?within?our?user?query?for?graphql
          func?(d?*Db)?GetUsersByName(name?string)?[]User?{
          ?//?Prepare?query,?takes?a?name?argument,?protects?from?sql?injection
          ?stmt,?err?:=?d.Prepare("SELECT?*?FROM?users?WHERE?name=$1")
          ?if?err?!=?nil?{
          ??fmt.Println("GetUserByName?Preperation?Err:?",?err)
          ?}

          ?//?Make?query?with?our?stmt,?passing?in?name?argument
          ?rows,?err?:=?stmt.Query(name)
          ?if?err?!=?nil?{
          ??fmt.Println("GetUserByName?Query?Err:?",?err)
          ?}

          ?//?Create?User?struct?for?holding?each?row's?data
          ?var?r?User
          ?//?Create?slice?of?Users?for?our?response
          ?users?:=?[]User{}
          ?//?Copy?the?columns?from?row?into?the?values?pointed?at?by?r?(User)
          ?for?rows.Next()?{
          ??err?=?rows.Scan(
          ???&r.ID,
          ???&r.Name,
          ???&r.Age,
          ???&r.Profession,
          ???&r.Friendly,
          ??)
          ??if?err?!=?nil?{
          ???fmt.Println("Error?scanning?rows:?",?err)
          ??}
          ??users?=?append(users,?r)
          ?}

          ?return?users
          }

          上面的思想是:創(chuàng)建數(shù)據(jù)庫(kù)的連接并返回持有該連接的Db對(duì)象。然后創(chuàng)建了一個(gè) dbGetUserByUsername() 方法。

          將關(guān)注點(diǎn)重新回到 main.go 文件,在 40 行處創(chuàng)建了一個(gè) root query 用于構(gòu)建 GraphQL 的 schema。我們?cè)?gql 包下的 queries.go 中創(chuàng)建:

          package?gql

          import?(
          ?"github.com/bradford-hamilton/go-graphql-api/postgres"
          ?"github.com/graphql-go/graphql"
          )

          //?Root?holds?a?pointer?to?a?graphql?object
          type?Root?struct?{
          ?Query?*graphql.Object
          }

          //?NewRoot?returns?base?query?type.?This?is?where?we?add?all?the?base?queries
          func?NewRoot(db?*postgres.Db)?*Root?{
          ?//?Create?a?resolver?holding?our?databse.?Resolver?can?be?found?in?resolvers.go
          ?resolver?:=?Resolver{db:?db}

          ?//?Create?a?new?Root?that?describes?our?base?query?set?up.?In?this
          ?//?example?we?have?a?user?query?that?takes?one?argument?called?name
          ?root?:=?Root{
          ??Query:?graphql.NewObject(
          ???graphql.ObjectConfig{
          ????Name:?"Query",
          ????Fields:?graphql.Fields{
          ?????"users":?&graphql.Field{
          ??????//?Slice?of?User?type?which?can?be?found?in?types.go
          ??????Type:?graphql.NewList(User),
          ??????Args:?graphql.FieldConfigArgument{
          ???????"name":?&graphql.ArgumentConfig{
          ????????Type:?graphql.String,
          ???????},
          ??????},
          ??????Resolve:?resolver.UserResolver,
          ?????},
          ????},
          ???},
          ??),
          ?}
          ?return?&root
          }

          NewRoot() 方法中傳入 db,并使用該 db 創(chuàng)建一個(gè) Resolver。在Resolver方法中對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作。

          然后創(chuàng)建了一個(gè) new root 用于用戶的查詢,需要name作為查詢參數(shù)。類型是 graphql.NewListUser(切片或者數(shù)組類型),在 gql 包下的 type.go 文件中定義。如果有其他類型的查詢,就在這個(gè) root 中增加。要把引入的 postgres 包改成自己本地的包。

          接下來(lái)看一下 resolvers.go:

          package?gql

          import?(
          ?"github.com/bradford-hamilton/go-graphql-api/postgres"
          ?"github.com/graphql-go/graphql"
          )

          //?Resolver?struct?holds?a?connection?to?our?database
          type?Resolver?struct?{
          ?db?*postgres.Db
          }

          //?UserResolver?resolves?our?user?query?through?a?db?call?to?GetUserByName
          func?(r?*Resolver)?UserResolver(p?graphql.ResolveParams)?(interface{},?error)?{
          ?//?Strip?the?name?from?arguments?and?assert?that?it's?a?string
          ?name,?ok?:=?p.Args["name"].(string)
          ?if?ok?{
          ??users?:=?r.db.GetUsersByName(name)
          ??return?users,?nil
          ?}

          ?return?nil,?nil
          }

          這里導(dǎo)入的 postgres 包同樣是你本地的。在這個(gè)地方還可以增加其他需要的解析器。

          接下來(lái)看 types.go

          package?gql

          import?"github.com/graphql-go/graphql"

          //?User?describes?a?graphql?object?containing?a?User
          var?User?=?graphql.NewObject(
          ?graphql.ObjectConfig{
          ??Name:?"User",
          ??Fields:?graphql.Fields{
          ???"id":?&graphql.Field{
          ????Type:?graphql.Int,
          ???},
          ???"name":?&graphql.Field{
          ????Type:?graphql.String,
          ???},
          ???"age":?&graphql.Field{
          ????Type:?graphql.Int,
          ???},
          ???"profession":?&graphql.Field{
          ????Type:?graphql.String,
          ???},
          ???"friendly":?&graphql.Field{
          ????Type:?graphql.Boolean,
          ???},
          ??},
          ?},
          )

          類似的,在這里添加我們不同的類型,每一個(gè)字段都指定了類型。在 main.go 文件的 42 行使用 root query 創(chuàng)建了一個(gè)新的查詢。

          差不多好了

          main.go 往下的 51 行處,創(chuàng)建一個(gè)新的 server,server 持有 GraphQL schema 的指針。下面是 server.go 的內(nèi)容:

          package?server

          import?(
          ?"encoding/json"
          ?"net/http"

          ?"github.com/bradford-hamilton/go-graphql-api/gql"
          ?"github.com/go-chi/render"
          ?"github.com/graphql-go/graphql"
          )

          //?Server?will?hold?connection?to?the?db?as?well?as?handlers
          type?Server?struct?{
          ?GqlSchema?*graphql.Schema
          }

          type?reqBody?struct?{
          ?Query?string?`json:"query"`
          }

          //?GraphQL?returns?an?http.HandlerFunc?for?our?/graphql?endpoint
          func?(s?*Server)?GraphQL()?http.HandlerFunc?{
          ?return?func(w?http.ResponseWriter,?r?*http.Request)?{
          ??//?Check?to?ensure?query?was?provided?in?the?request?body
          ??if?r.Body?==?nil?{
          ???http.Error(w,?"Must?provide?graphql?query?in?request?body",?400)
          ???return
          ??}

          ??var?rBody?reqBody
          ??//?Decode?the?request?body?into?rBody
          ??err?:=?json.NewDecoder(r.Body).Decode(&rBody)
          ??if?err?!=?nil?{
          ???http.Error(w,?"Error?parsing?JSON?request?body",?400)
          ??}

          ??//?Execute?graphql?query
          ??result?:=?gql.ExecuteQuery(rBody.Query,?*s.GqlSchema)

          ??//?render.JSON?comes?from?the?chi/render?package?and?handles
          ??//?marshalling?to?json,?automatically?escaping?HTML?and?setting
          ??//?the?Content-Type?as?application/json.
          ??render.JSON(w,?r,?result)
          ?}
          }

          在 server 中有一個(gè) GraphQL 的方法,這個(gè)方法的主要作用就是處理 GraphQL 的查詢。記得將 gql 的路徑更新為你本地的路徑。

          接下來(lái)看最后一個(gè)文件 gql.go

          package?gql

          import?(
          ?"fmt"

          ?"github.com/graphql-go/graphql"
          )

          //?ExecuteQuery?runs?our?graphql?queries
          func?ExecuteQuery(query?string,?schema?graphql.Schema)?*graphql.Result?{
          ?result?:=?graphql.Do(graphql.Params{
          ??Schema:????????schema,
          ??RequestString:?query,
          ?})

          ?//?Error?check
          ?if?len(result.Errors)?>?0?{
          ??fmt.Printf("Unexpected?errors?inside?ExecuteQuery:?%v",?result.Errors)
          ?}

          ?return?result
          }

          這里只有一個(gè)簡(jiǎn)單的 ExecuteQuery() 函數(shù)用來(lái)執(zhí)行 GraphQL 查詢。在這里可能會(huì)有一個(gè)類似于 ExecuteMutation() 函數(shù)用來(lái)處理 GraphQL 的 mutations。

          initializeAPI() 的最后,在 router 中增加一些中間工具,以及增加處理 /graphql POSTs 請(qǐng)求的 GraphQL server 方法。并且在這個(gè)地方增加其他 RESTful 請(qǐng)求的路由,并在 server 中增加處理路由的方法。

          然后在項(xiàng)目的根目錄運(yùn)行 realize init,會(huì)有兩次提示信息并且兩次都輸入 n

          下面是在你項(xiàng)目的根目錄下創(chuàng)建的 .realize.yaml 文件:

          settings:
          ??legacy:
          ?force:?false
          ?interval:?0s
          schema:
          -?name:?go-graphql-api
          ??path:?.
          ??commands:
          ?run:
          ???status:?true
          ??watcher:
          ?extensions:
          ?-?go
          ?paths:
          ?-?/
          ?ignored_paths:
          ?-?.git
          ?-?.realize
          ?-?vendor

          這段配置對(duì)于監(jiān)控你項(xiàng)目里面的改變非常重要,如果檢測(cè)到有改變,將自動(dòng)重啟 server 并重新運(yùn)行 main.go 文件。

          有一些開(kāi)發(fā) GraphQL API 非常好的工具,比如:graphiql[9]insomnia[10]graphql-playground[11],還可以發(fā)送一個(gè) application/json 請(qǐng)求體的 POST 請(qǐng)求,比如:

          {
          ?"query":?"{users(name:\"kevin\"){id,?name,?age}}"
          }

          Postman[12] 里像下面這樣:

          在查詢中可以只請(qǐng)求一個(gè)屬性或者多個(gè)屬性的組合。在 GraphQL 的正式版中,可以只請(qǐng)求我們希望通過(guò)網(wǎng)絡(luò)發(fā)送的信息。

          很成功

          大功告成!希望這篇博文對(duì)你在 Go 中編寫(xiě) GraphQL API 有幫助。我嘗試將功能分解到不同的包或文件中,使其更容易擴(kuò)展,而且每一塊也很容易測(cè)試。


          via: https://medium.com/@bradford_hamilton/building-an-api-with-graphql-and-go-9350df5c9356

          作者:Bradford Lamson-Scribner[13]譯者:HelloJavaWorld123[14]校對(duì):polaris1119[15]

          本文由 GCTT[16] 原創(chuàng)編譯,Go 中文網(wǎng)[17] 榮譽(yù)推出

          參考資料

          [1]

          realize: https://github.com/oxequa/realize

          [2]

          air: https://github.com/cosmtrek/air

          [3]

          Structuring Applications in Go: https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091

          [4]

          bradford-hamilton/go-graphql-api: https://github.com/github.com/bradford-hamilton/go-graphql-api

          [5]

          realize: https://github.com/oxequa/realize

          [6]

          chi: https://github.com/go-chi/chi

          [7]

          render: https://github.com/go-chi/render

          [8]

          graphql-go/graphql: https://github.com/graphql-go/graphql

          [9]

          graphiql: https://github.com/graphql/graphiql

          [10]

          insomnia: https://insomnia.rest/

          [11]

          graphql-playground: https://github.com/prisma/graphql-playground

          [12]

          Postman: https://www.getpostman.com/

          [13]

          Bradford Lamson-Scribner: https://medium.com/@bradford_hamilton

          [14]

          HelloJavaWorld123: https://github.com/HelloJavaWorld123

          [15]

          polaris1119: https://github.com/polaris1119

          [16]

          GCTT: https://github.com/studygolang/GCTT

          [17]

          Go 中文網(wǎng): https://studygolang.com/



          推薦閱讀


          福利

          我為大家整理了一份從入門(mén)到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門(mén)看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

          瀏覽 79
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.8x8x | 爱情岛成人亚洲WWW论坛 | αv天堂αv电影亚洲ωa | 久久中文网 | 最好看的MV中文字幕国语 |