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

Graphql In Golang

Bo-Yi Wu
July 18, 2018

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

Bo-Yi Wu

July 18, 2018
Tweet

More Decks by Bo-Yi Wu

Other Decks in Technology

Transcript

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

    Gin appleboy @ GitHub appleboy @ twitter appleboy @ slideshare appleboy46 @ facebook 2
  2. 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
  3. /** * @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. */
  4. { "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
  5. "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.", },
  6. "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
  7. // Schema is the GraphQL schema served by the server.

    var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: rootQuery, Mutation: rootMutation, Types: types, }) Query and Mutation
  8. WHY WE CHOOSE GOLANG? ▸Compiles Into Single Binary ▸Static Type

    System ▸Performance ▸Easy to Deploy ▸Learning Curve
  9. 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
  10. 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
  11. 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
  12. { "data": { "post": null }, "errors": [ { "message":

    "Internal Error: 404 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "post" ] } ] } single message error location
  13. 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
  14. 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
  15. 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
  16. "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
  17. 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
  18. 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
  19. 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
  20. 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