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

Bo-Yi Wu

July 18, 2018
Tweet

More Decks by Bo-Yi Wu

Other Decks in Technology

Transcript

  1. GRAPHQL IN GO
    MODERNWEB 2018

    View Slide

  2. ABOUT ME
    Open source contributor
    1. Gitea
    2. Drone
    3. Gin
    appleboy @ GitHub
    appleboy @ twitter
    appleboy @ slideshare
    appleboy46 @ facebook
    2

    View Slide

  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

    View Slide

  4. MOVING REST TO GRAPHQL
    icon from https://bit.ly/2LhOpZA

    View Slide

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

    View Slide

  6. 1. LARGE REQUEST IN SINGLE PAGE

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. UPDATE API
    GENERATE DOC
    BACKEND
    FRONTEND MOBILE
    TESTING

    View Slide

  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.
    */

    View Slide

  12. GRAPHQL

    View Slide

  13. WHAT IS GRAPHQL?
    IT IS A QUERY LANGUAGE

    View Slide

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

    View Slide

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

    View Slide

  16. {
    scene(id: 1) {
    name
    description
    items(type: DEVICE) {
    id
    name
    url
    }
    }
    }
    Graphql Request

    View Slide

  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

    View Slide

  18. /api/scene/1
    /api/scene/1/item
    /api/scene/1/all

    View Slide

  19. TYPE SYSTEM
    GRAPHQL LANGUAGE

    View Slide

  20. {
    myScene {
    name
    description
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  23. {
    scene(id: 1) {
    name
    description
    items(type: DEVICE) {
    id
    name
    url
    }
    }
    }
    Graphql Request

    View Slide

  24. type Scene {
    name: String!
    description: String
    items(type: ItemEnum): [Item]
    }
    enum ItemEnum {
    DEVICE,
    TEST_DEVICE,
    URL
    }

    View Slide

  25. {
    scene(id: 1) {
    name
    description
    items(type: DEVICE) {
    id
    name
    url
    }
    }
    }
    Graphql Request

    View Slide

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

    View Slide

  27. INTROSPECTION
    GRAPHQL LANGUAGE

    View Slide

  28. {
    __schema {
    queryType {
    name
    }
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  31. https://github.com/prismagraphql/graphql-playground

    View Slide

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

    View Slide

  33. RESOLVING FIELDS
    GRAPHQL LANGUAGE

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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.",
    },

    View Slide

  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

    View Slide

  39. MUTATION
    GRAPHQL LANGUAGE

    View Slide

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

    View Slide

  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

    View Slide

  42. SINGLE ENDPOINT
    POST /graphql

    View Slide

  43. GRAPHQL IN GOLANG
    WHY WE CHOOSE GOLANG?

    View Slide

  44. WHY WE CHOOSE GOLANG?
    ▸Compiles Into Single Binary
    ▸Static Type System
    ▸Performance
    ▸Easy to Deploy
    ▸Learning Curve

    View Slide

  45. BENCHMARK OF GRAPHQL FRAMEWORK IN GOLANG
    ▸graphql-go/graphql
    ▸playlyfe/go-graphql
    ▸graph-gophers/graphql-go
    ▸samsarahq/thunder

    View Slide

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

    View Slide

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

    View Slide

  48. View Slide

  49. API SERVER
    API SERVER
    GRAPHQL SERVER
    GRAPHQL GATEWAY

    View Slide

  50. GRAPHQL IN GOLANG
    PACKAGE IN GOLANG

    View Slide

  51. FRAMEWORK IN GO

    View Slide

  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

    View Slide

  53. JWT TOKEN
    SERVER
    GRAPHQL
    SERVER

    View Slide

  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

    View Slide

  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

    View Slide

  56. GRAPHQL ERROR
    BETTER ERROR HANDLING

    View Slide

  57. {
    "data": {
    "post": null
    },
    "errors": [
    {
    "message": "Internal Error: 404 not found",
    "locations": [
    {
    "line": 2,
    "column": 3
    }
    ],
    "path": [
    "post"
    ]
    }
    ]
    }
    single message
    error location

    View Slide

  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

    View Slide

  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

    View Slide

  60. GRAPHQL N+1
    DATA LOADER

    View Slide

  61. 1. LARGE REQUEST IN SINGLE PAGE

    View Slide

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

    View Slide

  63. ORM IN GOLANG
    GOORM VS XORM

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  67. GRAPHQL DATALOADER
    ONLY SUPPORT MEMORY, LRU OR TTL
    CACHE

    View Slide

  68. INTEGRATE WITH REDIS SERVICE
    GRAPHQL DATALOADER

    View Slide

  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

    View Slide

  70. HOW TO TEST GRAPHQL SCHEMA?
    GRAPHQL TESTING

    View Slide

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

    View Slide

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

    View Slide

  73. -
    id: 1
    email: [email protected]
    full_name: foo
    avatar: http://foo.com
    avatar_email: [email protected]
    -
    id: 2
    email: [email protected]
    full_name: bar
    avatar: http://bar.com
    avatar_email: [email protected]
    real data in DB

    View Slide

  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

    View Slide

  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

    View Slide

  76. all pass
    testing in sqlite

    View Slide

  77. GRAPHQL DEPLOYMENT
    HOW TO DEPLOY GOLANG APP?

    View Slide

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

    View Slide

  79. Thanks

    View Slide