Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

GraphQL IN Golang

  1. 1. GRAPHQL IN GO MODERNWEB 2018
  2. 2. ABOUT ME Open source contributor 1. Gitea 2. Drone 3. Gin appleboy @ GitHub appleboy @ twitter appleboy @ slideshare appleboy46 @ facebook 2
  3. 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. 4. MOVING REST TO GRAPHQL icon from https://bit.ly/2LhOpZA
  5. 5. 1. LARGE REQUEST IN SINGLE PAGE https://bit.ly/2NpS4Fu
  6. 6. 1. LARGE REQUEST IN SINGLE PAGE
  7. 7. CUSTOM FIELDS /api/scene/1?field=name,description,created_at
  8. 8. CUSTOMENDPOINT /api/scene/1/mobile?field=name,description /api/scene/1/web?field=name,description
  9. 9. DOCUMENTATION? HOW TO GENERATE DOCUMENT AUTOMATICALLY? API DOC Swagger
  10. 10. UPDATE API GENERATE DOC BACKEND FRONTEND MOBILETESTING
  11. 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. 12. GRAPHQL
  13. 13. WHATISGRAPHQL? IT IS A QUERY LANGUAGE
  14. 14. { myScene { name description } } LEXED PARSED VALIDATED EXECUTED
  15. 15. { scene(id: 1) { name description } } LEXED PARSED VALIDATED EXECUTED
  16. 16. { scene(id: 1) { name description items(type: DEVICE) { id name url } } } Graphql Request
  17. 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. 18. /api/scene/1 /api/scene/1/item /api/scene/1/all
  19. 19. TYPE SYSTEM GRAPHQL LANGUAGE
  20. 20. { myScene { name description } }
  21. 21. { scene(id: 1) { name description items(type: DEVICE) { id name url } } }
  22. 22. type QueryRoot { myScene: Scene scene(id: ID!): Scene }
  23. 23. { scene(id: 1) { name description items(type: DEVICE) { id name url } } } Graphql Request
  24. 24. type Scene { name: String! description: String items(type: ItemEnum): [Item] } enum ItemEnum { DEVICE, TEST_DEVICE, URL }
  25. 25. { scene(id: 1) { name description items(type: DEVICE) { id name url } } } Graphql Request
  26. 26. type Item { id: ID! name: String! url: String }
  27. 27. INTROSPECTION GRAPHQL LANGUAGE
  28. 28. { __schema { queryType { name } } }
  29. 29. { "data": { "__schema": { "queryType": { "name": "Query" } } } }
  30. 30. WHY NEED THE INTROSPECTION ▸code generation ▸auto documentation ▸static validation ▸IDE Integration
  31. 31. https://github.com/prismagraphql/graphql-playground
  32. 32. https://www.prisma.io/blog/introducing-graphql-playground-f1e0a018f05d/
  33. 33. RESOLVINGFIELDS GRAPHQL LANGUAGE
  34. 34. type Item { id: ID type: sceneEnum name: String }
  35. 35.     "id": &graphql.Field{       Type: graphql.Int,     }, INT FLOAT STRING BOOLEAN ID
  36. 36. type Item { id: ID type: sceneEnum name: String }
  37. 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. 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. 39. MUTATION GRAPHQL LANGUAGE
  40. 40. mutation { createScene( title: "foo", description: "bar" ) { id title description url } }
  41. 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. 42. SINGLE ENDPOINT POST /graphql
  43. 43. GRAPHQL IN GOLANG WHY WE CHOOSE GOLANG?
  44. 44. WHY WE CHOOSE GOLANG? ▸Compiles Into Single Binary ▸Static Type System ▸Performance ▸Easy to Deploy ▸Learning Curve
  45. 45. BENCHMARKOFGRAPHQLFRAMEWORKINGOLANG ▸graphql-go/graphql ▸playlyfe/go-graphql ▸graph-gophers/graphql-go ▸samsarahq/thunder
  46. 46. https://github.com/appleboy/golang-graphql-benchmark $ go test -v -bench=. -benchmem
  47. 47. VEKTAH/GQLGEN GO GENERATE BASED GRAPHQL SERVER LIBRARY https://github.com/vektah/gqlgenhttps://youtu.be/3dpqYMqyiOg
  48. 48. API SERVER API SERVER GRAPHQLSERVER GRAPHQL GATEWAY
  49. 49. GRAPHQLINGOLANG PACKAGE IN GOLANG
  50. 50. FRAMEWORKINGO
  51. 51. 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
  52. 52. JWT TOKEN SERVER GRAPHQL SERVER
  53. 53. 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
  54. 54. 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
  55. 55. GRAPHQL ERROR BETTER ERROR HANDLING
  56. 56. { "data": { "post": null }, "errors": [ { "message": "Internal Error: 404 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "post" ] } ] } single message error location
  57. 57. 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
  58. 58. 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
  59. 59. GRAPHQL N+1 DATA LOADER
  60. 60. 1. LARGE REQUEST IN SINGLE PAGE
  61. 61. { myScene(id: 1) { name description role { id user { email } model } } }
  62. 62. ORM IN GOLANG GOORM VS XORM
  63. 63. 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
  64. 64.  "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
  65. 65. 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
  66. 66. GRAPHQLDATALOADER ONLY SUPPORT MEMORY, LRU OR TTL CACHE
  67. 67. INTEGRATE WITH REDIS SERVICE GRAPHQLDATALOADER
  68. 68. 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
  69. 69. HOW TO TEST GRAPHQL SCHEMA? GRAPHQL TESTING
  70. 70. https://code.likeagirl.io/the-7-steps-to-a-complete-code-review-abdfd39e75f1
  71. 71. RAILS-LIKE TEST FIXTURES FOR GO GO TEST FIXTURES https://github.com/go-testfixtures/testfixtures
  72. 72. - 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
  73. 73.     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
  74. 74.   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
  75. 75. all pass testing in sqlite
  76. 76. GRAPHQLDEPLOYMENT HOW TO DEPLOY GOLANG APP?
  77. 77. GO-GGZ/GGZ AN URL SHORTENER SERVICE WRITTEN IN GO https://github.com/go-ggz/ggz
  78. 78. Thanks

×