Vasiliy  Shakhunov
Vasiliy Shakhunov

Reputation: 398

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?

Upvotes: 0

Views: 1991

Answers (4)

mparis
mparis

Reputation: 3683

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 :)

Upvotes: 3

MaiKaY
MaiKaY

Reputation: 4482

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).

Upvotes: 3

Marco Daniel
Marco Daniel

Reputation: 5765

Apollo GraphQL Server provides a very good setup to deploy a GraphQL server in AWS Lambda.

Upvotes: 0

Gabriel Bleu
Gabriel Bleu

Reputation: 10224

You can proxy all the queries to a graphql-server

Upvotes: 0

Related Questions