Direct Lambda Resolver. A custom AppSync Resolver to bypass the use of Apache Velocity Template (VTL) and automatically map your function's response to a GraphQL field.
Amplify GraphQL Transformer. Custom GraphQL directives to define your application's data model using Schema Definition Language (SDL). Amplify CLI uses these directives to convert GraphQL SDL into full descriptive AWS CloudFormation templates.
You must have an existing AppSync GraphQL API and IAM permissions to invoke your Lambda function. That said, there is no additional permissions to use this utility.
This is the sample infrastructure we are using for the initial examples with a AppSync Direct Lambda Resolver.
AWSTemplateFormatVersion:'2010-09-09'Transform:AWS::Serverless-2016-10-31Description:Hello world Direct Lambda ResolverGlobals:Function:Timeout:5Runtime:python3.8Tracing:ActiveEnvironment:Variables:# Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/latest/#environment-variablesLOG_LEVEL:INFOPOWERTOOLS_LOGGER_SAMPLE_RATE:0.1POWERTOOLS_LOGGER_LOG_EVENT:truePOWERTOOLS_SERVICE_NAME:sample_resolverResources:HelloWorldFunction:Type:AWS::Serverless::FunctionProperties:Handler:app.lambda_handlerCodeUri:hello_worldDescription:Sample Lambda Powertools Direct Lambda ResolverTags:SOLUTION:LambdaPowertoolsPython# IAM Permissions and RolesAppSyncServiceRole:Type:"AWS::IAM::Role"Properties:AssumeRolePolicyDocument:Version:"2012-10-17"Statement:-Effect:"Allow"Principal:Service:-"appsync.amazonaws.com"Action:-"sts:AssumeRole"InvokeLambdaResolverPolicy:Type:"AWS::IAM::Policy"Properties:PolicyName:"DirectAppSyncLambda"PolicyDocument:Version:"2012-10-17"Statement:-Effect:"Allow"Action:"lambda:invokeFunction"Resource:-!GetAttHelloWorldFunction.ArnRoles:-!RefAppSyncServiceRole# GraphQL APIHelloWorldApi:Type:"AWS::AppSync::GraphQLApi"Properties:Name:HelloWorldApiAuthenticationType:"API_KEY"XrayEnabled:trueHelloWorldApiKey:Type:AWS::AppSync::ApiKeyProperties:ApiId:!GetAttHelloWorldApi.ApiIdHelloWorldApiSchema:Type:"AWS::AppSync::GraphQLSchema"Properties:ApiId:!GetAttHelloWorldApi.ApiIdDefinition:|schema {query:Query}type Query {getTodo(id: ID!): TodolistTodos: [Todo]}type Todo {id: ID!title: Stringdescription: Stringdone: Boolean}# Lambda Direct Data Source and ResolverHelloWorldFunctionDataSource:Type:"AWS::AppSync::DataSource"Properties:ApiId:!GetAttHelloWorldApi.ApiIdName:"HelloWorldLambdaDirectResolver"Type:"AWS_LAMBDA"ServiceRoleArn:!GetAttAppSyncServiceRole.ArnLambdaConfig:LambdaFunctionArn:!GetAttHelloWorldFunction.ArnListTodosResolver:Type:"AWS::AppSync::Resolver"Properties:ApiId:!GetAttHelloWorldApi.ApiIdTypeName:"Query"FieldName:"listTodos"DataSourceName:!GetAttHelloWorldFunctionDataSource.NameGetTodoResolver:Type:"AWS::AppSync::Resolver"Properties:ApiId:!GetAttHelloWorldApi.ApiIdTypeName:"Query"FieldName:"getTodo"DataSourceName:!GetAttHelloWorldFunctionDataSource.NameOutputs:HelloWorldFunction:Description:"HelloWorldLambdaFunctionARN"Value:!GetAttHelloWorldFunction.ArnHelloWorldAPI:Value:!GetAttHelloWorldApi.Arn
You can define your functions to match GraphQL types and fields with the app.resolver() decorator.
Here's an example where we have two separate functions to resolve getTodo and listTodos fields within the Query type. For completion, we use Scalar type utilities to generate the right output based on our schema definition.
GraphQL arguments are passed as function arguments
fromaws_lambda_powertoolsimportLogger,Tracerfromaws_lambda_powertools.loggingimportcorrelation_pathsfromaws_lambda_powertools.event_handlerimportAppSyncResolverfromaws_lambda_powertools.utilities.data_classes.appsyncimportscalar_types_utilstracer=Tracer(service="sample_resolver")logger=Logger(service="sample_resolver")app=AppSyncResolver()# Note that `creation_time` isn't available in the schema# This utility also takes into account what info you make available at API level vs what's storedTODOS=[{"id":scalar_types_utils.make_id(),# type ID or String"title":"First task","description":"String","done":False,"creation_time":scalar_types_utils.aws_datetime(),# type AWSDateTime},{"id":scalar_types_utils.make_id(),"title":"Second task","description":"String","done":True,"creation_time":scalar_types_utils.aws_datetime(),},]@app.resolver(type_name="Query",field_name="getTodo")defget_todo(id:str=""):logger.info(f"Fetching Todo {id}")todo=[todofortodoinTODOSiftodo["id"]==id]returntodo@app.resolver(type_name="Query",field_name="listTodos")deflist_todos():returnTODOS@logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER)@tracer.capture_lambda_handlerdeflambda_handler(event,context):returnapp.resolve(event,context)
Amplify CLI generated functions use Pipenv as a dependency manager
Your function source code is located at amplify/backend/function/your-function-name.
Within your function's folder, add Lambda Powertools as a dependency with pipenv install aws-lambda-powertools.
Use the following code for merchantInfo and searchMerchant functions respectively.
1 2 3 4 5 6 7 8 91011121314151617181920212223
fromaws_lambda_powertoolsimportLogger,Tracerfromaws_lambda_powertools.loggingimportcorrelation_pathsfromaws_lambda_powertools.event_handlerimportAppSyncResolverfromaws_lambda_powertools.utilities.data_classes.appsyncimportscalar_types_utilstracer=Tracer(service="sample_graphql_transformer_resolver")logger=Logger(service="sample_graphql_transformer_resolver")app=AppSyncResolver()@app.resolver(type_name="Query",field_name="listLocations")deflist_locations(page:int=0,size:int=10):return[{"id":100,"name":"Smooth Grooves"}]@app.resolver(field_name="commonField")defcommon_field():# Would match all fieldNames matching 'commonField'returnscalar_types_utils.make_id()@tracer.capture_lambda_handler@logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER)deflambda_handler(event,context):app.resolve(event,context)
You can test your resolvers by passing a mocked or actual AppSync Lambda event that you're expecting.
You can use either app.resolve(event, context) or simply app(event, context).
Here's an example from our internal functional test.
1 2 3 4 5 6 7 8 910111213141516
deftest_direct_resolver():# Check whether we can handle an example appsync direct resolver# load_event primarily deserialize the JSON event into a dictmock_event=load_event("appSyncDirectResolver.json")app=AppSyncResolver()@app.resolver(field_name="createSomething")defcreate_something(id:str):assertapp.lambda_context=={}returnid# Call the implicit handlerresult=app(mock_event,{})assertresult=="my identifier"