Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Graphql In Golang

Graphql In Golang

1. Why we moving API from REST to Graphql?
2. What is Graphql?
3. Graphql in Golang (Why we choose Golang)
4. How to testing Graphql in Golang
5. Deploy Graphql application

265bcbb56e831266de7a9f9281aab57a?s=128

Bo-Yi Wu

July 18, 2018
Tweet

Transcript

  1. GRAPHQL IN GO MODERNWEB 2018

  2. ABOUT ME Open source contributor 1. Gitea 2. Drone 3.

    Gin appleboy @ GitHub appleboy @ twitter appleboy @ slideshare appleboy46 @ facebook 2
  3. AGENDA ▸ Why we moving API from REST to Graphql?

    ▸ What is Graphql? ▸ Graphql in Golang (Why we choose Golang) ▸ How to testing Graphql in Golang ▸ Deploy Graphql application
  4. MOVING REST TO GRAPHQL icon from https://bit.ly/2LhOpZA

  5. 1. LARGE REQUEST IN SINGLE PAGE https://bit.ly/2NpS4Fu

  6. 1. LARGE REQUEST IN SINGLE PAGE

  7. CUSTOM FIELDS /api/scene/1?field=name,description,created_at

  8. CUSTOM ENDPOINT /api/scene/1/mobile?field=name,description /api/scene/1/web?field=name,description

  9. DOCUMENTATION? HOW TO GENERATE DOCUMENT AUTOMATICALLY? API DOC Swagger

  10. UPDATE API GENERATE DOC BACKEND FRONTEND MOBILE TESTING

  11. /** * @api {get} /scene/:id Request Scene information * @apiName

    GetScene * @apiGroup Scene * * @apiParam {Number} id Scenes unique ID. * * @apiSuccess {String} title Title of the Scene. * @apiSuccess {String} desc Description of the Scene. */
  12. GRAPHQL

  13. WHAT IS GRAPHQL? IT IS A QUERY LANGUAGE

  14. { myScene { name description } } LEXED PARSED VALIDATED

    EXECUTED
  15. { scene(id: 1) { name description } } LEXED PARSED

    VALIDATED EXECUTED
  16. { scene(id: 1) { name description items(type: DEVICE) { id

    name url } } } Graphql Request
  17. { "scene": { "name": "foo", "description": “bar", "items": [{ "id":

    1, "name": "test_1", "url": "http://foo.com" }, { "id": 2, "name": "test_2", "url": "http://bar.com" }] } } JSON Response
  18. /api/scene/1 /api/scene/1/item /api/scene/1/all

  19. TYPE SYSTEM GRAPHQL LANGUAGE

  20. { myScene { name description } }

  21. { scene(id: 1) { name description items(type: DEVICE) { id

    name url } } }
  22. type QueryRoot { myScene: Scene scene(id: ID!): Scene }

  23. { scene(id: 1) { name description items(type: DEVICE) { id

    name url } } } Graphql Request
  24. type Scene { name: String! description: String items(type: ItemEnum): [Item]

    } enum ItemEnum { DEVICE, TEST_DEVICE, URL }
  25. { scene(id: 1) { name description items(type: DEVICE) { id

    name url } } } Graphql Request
  26. type Item { id: ID! name: String! url: String }

  27. INTROSPECTION GRAPHQL LANGUAGE

  28. { __schema { queryType { name } } }

  29. { "data": { "__schema": { "queryType": { "name": "Query" }

    } } }
  30. WHY NEED THE INTROSPECTION ▸code generation ▸auto documentation ▸static validation

    ▸IDE Integration
  31. https://github.com/prismagraphql/graphql-playground

  32. https://www.prisma.io/blog/introducing-graphql-playground-f1e0a018f05d/

  33. RESOLVING FIELDS GRAPHQL LANGUAGE

  34. type Item { id: ID type: sceneEnum name: String }

  35. "id": &graphql.Field{ Type: graphql.Int, }, INT FLOAT STRING BOOLEAN ID

  36. type Item { id: ID type: sceneEnum name: String }

  37. "TEST_DEVICE": &graphql.EnumValueConfig{ Value: int(model.TypeTestDevice), Description: "test device.", }, "DEVICE": &graphql.EnumValueConfig{

    Value: int(model.TypeDevice), Description: "device.", }, "URL": &graphql.EnumValueConfig{ Value: int(model.TypeURL), Description: "url.", },
  38. "name": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) {

    o, _ := p.Source.(*model.Scene) return "Hello, " + o.Name, nil }, }, Return Custom Value
  39. MUTATION GRAPHQL LANGUAGE

  40. mutation { createScene( title: "foo", description: "bar" ) { id

    title description url } }
  41. // Schema is the GraphQL schema served by the server.

    var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: rootQuery, Mutation: rootMutation, Types: types, }) Query and Mutation
  42. SINGLE ENDPOINT POST /graphql

  43. GRAPHQL IN GOLANG WHY WE CHOOSE GOLANG?

  44. WHY WE CHOOSE GOLANG? ▸Compiles Into Single Binary ▸Static Type

    System ▸Performance ▸Easy to Deploy ▸Learning Curve
  45. BENCHMARK OF GRAPHQL FRAMEWORK IN GOLANG ▸graphql-go/graphql ▸playlyfe/go-graphql ▸graph-gophers/graphql-go ▸samsarahq/thunder

  46. https://github.com/appleboy/golang-graphql-benchmark $ go test -v -bench=. -benchmem

  47. VEKTAH/GQLGEN GO GENERATE BASED GRAPHQL SERVER LIBRARY https://github.com/vektah/gqlgen https://youtu.be/3dpqYMqyiOg

  48. None
  49. API SERVER API SERVER GRAPHQL SERVER GRAPHQL GATEWAY

  50. GRAPHQL IN GOLANG PACKAGE IN GOLANG

  51. FRAMEWORK IN GO

  52. func Handler() gin.HandlerFunc { h := handler.New(&handler.Config{ Schema: &schema.Schema, Pretty:

    true, }) return func(c *gin.Context) { h.ServeHTTP(c.Writer, c.Request) } } graphql schema
  53. JWT TOKEN SERVER GRAPHQL SERVER

  54. func Check() gin.HandlerFunc { return func(c *gin.Context) { token :=

    c.GetHeader("Authorization") user, err := authUser(token) if token != "" && err != nil { logrus.Errorf("authUser error: %s", err.Error()) } if user != nil && user.Email != "" { c.Set("email", user.Email) c.Set("user_id", user.UserID) } ctx := context.WithValue( c.Request.Context(), config.ContextKeyUser, user, ) c.Request = c.Request.WithContext(ctx) } } store user data
  55. var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: rootQuery, Mutation: rootMutation, })

    var rootMutation = graphql.NewObject( graphql.ObjectConfig{ Name: "RootMutation", Description: "Root Mutation", Fields: graphql.Fields{ "updateUser": &updateUser, "createScene": &createScene, "updateScene": &updateScene, "deleteScene": &deleteScene, }, }) var rootQuery = graphql.NewObject( graphql.ObjectConfig{ Name: "RootQuery", Description: "Root Query", Fields: graphql.Fields{ "queryUser": &queryUser, "searchUser": &searchUser, "queryScene": &queryScene, }, }) query mutation
  56. GRAPHQL ERROR BETTER ERROR HANDLING

  57. { "data": { "post": null }, "errors": [ { "message":

    "Internal Error: 404 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "post" ] } ] } single message error location
  58. func (g FormattedError) MarshalJSON() ([]byte, error) { m := map[string]interface{}{}

    for k, v := range g.Extensions { m[k] = v } m["message"] = g.Message m["locations"] = g.Locations return json.Marshal(m) } Marshal JSON custom key value
  59. func FormatError(err error, arg ...interface{}) gqlerrors.FormattedError { switch err :=

    err.(type) { case gqlerrors.FormattedError: return err case *Error: return gqlerrors.FormattedError{ Message: fmt.Sprintf(err.Error(), arg...), Extensions: gqlerrors.ErrorExtensions{ "code": err.Type.Code(), "type": err.Type, }, } } custom error original error
  60. GRAPHQL N+1 DATA LOADER

  61. 1. LARGE REQUEST IN SINGLE PAGE

  62. { myScene(id: 1) { name description role { id user

    { email } model } } }
  63. ORM IN GOLANG GOORM VS XORM

  64. type Scene struct { ID int64 `xorm:"pk autoincr" json:"id"` Image

    string `json:"image,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` DeletedAt time.Time `xorm:"deleted"` // reference Items []*SceneItem `xorm:"-" json:"items,omitempty"` User *User `xorm:"-" json:"user,omitempty"` Role *SceneAccess `xorm:"-" json:"role,omitempty"` } Data Model user role permission
  65. "user": &graphql.Field{ Type: userType, Resolve: func(p graphql.ResolveParams) (interface{}, error) {

    o, ok := p.Source.(*model.Shorten) if !ok { return nil, errMissingSource } if o.User != nil { return o.User, nil } return getUserFromLoader(p.Context, o.UserID) }, }, exist in model? fetch from loader
  66. GET DATA FROM DATABASE IF NOT EXIST func userBatch(ctx context.Context,

    keys dataloader.Keys) []*dataloader.Result { var results []*dataloader.Result id, _ := helper.GetCacheID(keys[0].String()) user, err := model.GetUserByID(id.(int64)) results = append(results, &dataloader.Result{ Data: user, Error: err, }) return results } fetch from DB
  67. GRAPHQL DATALOADER ONLY SUPPORT MEMORY, LRU OR TTL CACHE

  68. INTEGRATE WITH REDIS SERVICE GRAPHQL DATALOADER

  69. func batchFunc(_ context.Context, keys dataloader.Keys) []*dataloader.Result { results := make([]*dataloader.Result,

    len(keys)) // get what you can from redis values, _ := redisClient.MGet(...keys.Keys()).Result() // make a list of everything that was not found in redis var cacheMisses map[int]string for i := range keys { if values[i] == redis.Nil { cacheMisses[i] = keys[i].String() } else { results[i] = &dataloader.Result{values[i], nil} } } // get the missing items from more expensive location (like DB) for idx, key := range cacheMisses { value, err := db.GetValues(key) // Pseudo code! redisClient.Set(key, value) results[idx] = &dataloader.Result{value, err} } return results } miss from redis fetch from DB
  70. HOW TO TEST GRAPHQL SCHEMA? GRAPHQL TESTING

  71. https://code.likeagirl.io/the-7-steps-to-a-complete-code-review-abdfd39e75f1

  72. RAILS-LIKE TEST FIXTURES FOR GO GO TEST FIXTURES https://github.com/go-testfixtures/testfixtures

  73. - id: 1 email: foo@gmail.com full_name: foo avatar: http://foo.com avatar_email:

    foo@gmail.com - id: 2 email: bar@gmail.com full_name: bar avatar: http://bar.com avatar_email: bar@gmail.com real data in DB
  74. test := T{ Query: ` query QueryShortenURL ( $slug: String!

    ) { QueryShortenURL(slug: $slug) { url } } `, Schema: Schema, Expected: &graphql.Result{ Data: map[string]interface{}{ "QueryShortenURL": map[string]interface{}{ "url": "http://example.com", }, }, }, } graphql query expect data
  75. params := graphql.Params{ Schema: test.Schema, RequestString: test.Query, Context: ctx, VariableValues:

    map[string]interface{}{ "slug": "abcdef", }, } testGraphql(test, params, t) ctx := newContextWithUser(context.TODO(), user) user data in context graphql variable
  76. all pass testing in sqlite

  77. GRAPHQL DEPLOYMENT HOW TO DEPLOY GOLANG APP?

  78. GO-GGZ/GGZ AN URL SHORTENER SERVICE WRITTEN IN GO https://github.com/go-ggz/ggz

  79. Thanks