简体   繁体   中英

Best approach to handle graphql for aws lambda?

I'm following the tutorial https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html

And have some doubts for using just a switch to handle graphql queries.

Is there a better approach to handle more complicated requests?

The choice is yours as to how to setup lambda within your AppSync API. It is entirely reasonable to have a lambda function per resolver and have a function be responsible for a single resolver. You can alternatively take an approach like the tutorial and use a single function and some lightweight routing code to take care of calling the correct function. Using a single function can often offer some performance benefits because of how lambda's container warming works (esp. for Java & C# where VM startup time can add up) but has less separation of concerns.

Here are some approaches I have taken in the past:

Option 1: JS

This approach uses JavaScript and should feel familiar to those who have run their own GraphQL servers before.

const Resolvers = {
  Query: {
    me: (source, args, identity) => getLoggedInUser(args, identity)
  },
  Mutation: {
    login: (source, args, identity) => loginUser(args, identity)
  }
}

exports.handler = (event, context, callback) => {
    // We are going to wire up the resolver to give all this information in this format.
    const { TypeName, FieldName, Identity, Arguments, Source } = event

    const typeResolver = Resolvers[TypeName]
    if (!typeResolver) {
      return callback(new Error(`No resolvers found for type: "${TypeName}"`))
    }
    const fieldResolver = typeResolver[FieldName]
    if (!fieldResolver) {
      return callback(new Error(`No resolvers found for field: "${FieldName}" on type: "${TypeName}"`), null)
    }
    // Handle promises as necessary.
    const result = fieldResolver(Source, Arguments, Identity);
    return callback(null, result)
};

You can then use a standard lambda resolver from AppSync. For now we have to provide the TypeName and FieldName manually.

#**
    The value of 'payload' after the template has been evaluated
    will be passed as the event to AWS Lambda.
*#
{
    "version" : "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "TypeName": "Query",
        "FieldName": "me",
        "Arguments": $util.toJson($context.arguments),
        "Identity": $util.toJson($context.identity),
        "Source": $util.toJson($context.source)
    }
}

Option 2: Go

For the curious, I have also used go lambda functions successfully with AppSync. Here is one approach that has worked well for me.

package main

import (
  "context"
  "fmt"

  "github.com/aws/aws-lambda-go/lambda"
  "github.com/fatih/structs"
  "github.com/mitchellh/mapstructure"
)

type GraphQLPayload struct {
  TypeName    string                 `json:"TypeName"`
  FieldName   string                 `json:"FieldName"`
  Arguments   map[string]interface{} `json:"Arguments"`
  Source      map[string]interface{} `json:"Source"`
  Identity    map[string]interface{} `json:"Identity"`
}

type ResolverFunction func(source, args, identity map[string]interface{}) (data map[string]interface{}, err error)

type TypeResolverMap = map[string]ResolverFunction

type SchemaResolverMap = map[string]TypeResolverMap

func resolverMap() SchemaResolverMap {
  return map[string]TypeResolverMap{
    "Query": map[string]ResolverFunction{
      "me": getLoggedInUser,
    },
  }
}

func Handler(ctx context.Context, event GraphQLPayload) (map[string]interface{}, error) {
  // Almost the same as the JS option.
  resolvers := resolverMap()
  typeResolver := resolvers[event.TypeName]
  if typeResolver == nil {
    return nil, fmt.Errorf("No type resolver for type " + event.TypeName)
  }
  fieldResolver := typeResolver[event.FieldName]
  if fieldResolver == nil {
    return nil, fmt.Errorf("No field resolver for field " + event.FieldName)
  }
  return fieldResolver(event.Source, event.Arguments, event.Identity)
}

func main() {
  lambda.Start(Handler)
}

/**
* Resolver Functions
 */

/**
 * Get the logged in user
 */
func getLoggedInUser(source, args, identity map[string]interface{}) (data map[string]interface{}, err error) {

  // Decode the map[string]interface{} into a struct I defined
  var typedArgs myModelPackage.GetLoggedInUserArgs
  err = mapstructure.Decode(args, &typedArgs)
  if err != nil {
    return nil, err
  }

  // ... do work
  res, err := auth.GetLoggedInUser()
  if err != nil {
    return nil, err
  }

  // Map the struct back to a map[string]interface{}
  return structs.Map(out), nil
}

// ... Add as many more as needed

You can then use the same resolver template as used in option 1. There are many other ways to do this but this is one method that has worked well for me.

Hope this helps :)

You are not forced to use one single AWS Lambda to handle each request. For this tutorial it's easier for newcomers to get the idea of it, therefore they used this approach.

But it's up to you how to implement it in the end. An alternative would be to create for each resolver a separate AWS Lambda to eliminate the switch and to follow Single Responsibility Principle (SRP) .

您可以将所有查询代理到graphql服务器

Apollo GraphQL Server提供了一个非常好的设置,可以在AWS Lambda中部署GraphQL服务器。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM