Enhancing User Experience with AppSync and Step Functions
Written on
Chapter 1: Introduction
In today's fast-paced digital world, users expect quick responses after submitting a form. No one wants to wait for what seems like an eternity, right? Let's delve into an event-driven strategy that utilizes AppSync and Step Functions, providing a swift and efficient experience for form submissions. This guide is structured in concise sections with useful documentation for further exploration.
Section 1.1: Setting Up the SAM Template
To kick things off, we will employ the Serverless Application Model (SAM) macro from AWS. This feature enables us to streamline the process by automating the creation of resources, including the state machine. To get started, add the following to your SAM template:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
GraphQL API linked to a state machine
Parameters:
// Provide necessary parameters, such as stage for resource name prefixes
Resources:
// Ensure proper indentation for new resources
Section 1.2: Configuring the GraphQL API
At a minimum, you will need to define a name and an authentication type for your API. Reference the AWS::AppSync::GraphQLApi documentation for additional fields you may require.
GraphQLApi:
Type: AWS::AppSync::GraphQLApi
Properties:
Name: MyApi
AuthenticationType: AWS_IAM
Subsection 1.2.1: Setting Up the Schema
For clarity, I've included an inline schema definition, but you may also store it in a separate file under the DefinitionS3Location property. Your FormInput should encapsulate any relevant fields. Since this form involves data writing operations, it will utilize a mutation, specifically named submitToForm.
GraphQLApiSchema:
Type: AWS::AppSync::GraphQLSchema
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
Definition: |
type FormResponse {
status: String}
input FormInput {
id: String
otherFields: [String]
}
type Mutation {
submitToForm(input: FormInput) : FormResponse}
schema {
mutation: Mutation}
Section 1.3: Setting Up the State Machine
Now, let's configure the state machine. To ensure an optimal user experience, we aim for rapid responses, even during simultaneous form submissions. For this, an EXPRESS workflow is ideal. Below are examples of supported tasks within an EXPRESS workflow. You will need to establish permissions based on your specific requirements.
StateMachine:
Type: AWS::Serverless::StateMachine
Properties:
Name: sample-state-machine
Type: EXPRESS
Definition:
Comment: An example of the Amazon States Language
StartAt: Validate Form
States:
Validate Form:
Type: Task
Resource: arn:aws:states:::lambda:invoke
OutputPath: "$.Payload"
Parameters:
Payload.$: "$"
FunctionName: "${ValidateFormLambdaArn}"
Retry:
ErrorEquals:
- Lambda.ServiceException
- Lambda.AWSLambdaException
- Lambda.SdkClientException
IntervalSeconds: 2
MaxAttempts: 6
BackoffRate: 2
Next: Send an EventBridge custom event
Send an EventBridge custom event:
Type: Task
Resource: 'arn:aws:states:::events:putEvents'
Parameters:
Entries:
Detail:
Message: 'Hello from Step Functions!'
DetailType: MyDetailType
EventBusName: MyEventBusName
Source: MySource
Next: Save Form Data Details
Save Form Data Details:
Type: Task
Parameters:
TransactItems.$: "$.items"Resource: arn:aws:states:::aws-sdk:dynamodb:transactWriteItems
InputPath: "$.transactWrite"
ResultPath: "$.transactWriteItemResponse"
Next: Format Return Response
Format Return Response:
Type: Pass
End: true
InputPath: "$.response"
ResultPath: "$.input"
OutputPath: "$.input"
Section 1.4: Establishing Permissions
This IAM Role is crucial as it enables AppSync to initiate synchronous executions.
ServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
Effect: "Allow"
Principal:
Service: "appsync.amazonaws.com"
Action: "sts:AssumeRole"
Path: "/"
Policies:
PolicyName: "dev-start-sync-execution"
PolicyDocument:
Version: "2012-10-17"
Statement:
Effect: "Allow"
Action: "states:StartSyncExecution"
Resource: !Ref StateMachine
Chapter 2: Configuring the Data Source and Resolver
Section 2.1: Defining the Data Source
A data source represents the AWS component that your GraphQL API will interact with, in this case, a step function. It's essential for it to be able to assume the service role. The HTTP configuration should point to the AWS endpoint for synchronous states.
StepFunctionHTTPDataSource:
Type: AWS::AppSync::DataSource
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
Type: HTTP
ServiceRoleArn: !GetAtt ServiceRole.Arn
HttpConfig:
AuthorizationConfig:
AuthorizationType: AWS_IAM
AwsIamConfig:
SigningRegion: !Ref AWS::Region
SigningServiceName: states
Section 2.2: Configuring the Resolver
This is where everything converges! As previously mentioned, we utilize a mutation. The ApiId corresponds to the configured API, and FieldName is submitToForm, as defined in our schema. The data source is linked to the synchronous states execution endpoint, which has the appropriate service role.
AppSyncMutationToStepFunction:
Type: AWS::AppSync::Resolver
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
TypeName: Mutation
FieldName: submitToForm
DataSourceName: !Ref StepFunctionHTTPDataSource.Name
RequestMappingTemplate: !Sub >
{
#set( $input = $ctx.arguments.input )
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/",
"params": {
"headers": {
"content-type": "application/x-amz-json-1.0",
"x-amz-target": "AWSStepFunctions.StartSyncExecution"
},
"body": {
"stateMachineArn": "${StateMachine}",
"input": "$util.escapeJavaScript($util.toJson($input))"
}
}
}
ResponseMappingTemplate: >
#if($context.error)
$util.error($context.error.message, $context.error.type)#else
#set( $body = $util.parseJson($context.result.body) )
$body.output
#end
Wrapping Up
A significant aspect of event-driven development is the separation of logic. By employing a step function to manage form submissions and dispatch events throughout the workflow, we enhance efficiency. The EXPRESS type is particularly beneficial for handling numerous requests, ensuring they take less than five minutes to complete.
I am not providing specific ASL JSON since your exact requirements may vary, but the best practice is to emit an event based on the input data. You also have various commands available, such as writing items to DynamoDB or executing a Lambda function. The key is to ensure that all steps remain within the five-minute threshold for optimal speed.
Ultimately, users are indifferent to what occurs behind the scenes; they simply need assurance that their form submission was successful and they received a confirmation. With an event-driven approach, we can efficiently manage processing while maintaining quick response times.
The video titled "AWS Serverless Project: Contact Form Application using Lambda, S3, DynamoDB, API Gateway & Cognito" demonstrates the practical implementation of these concepts, providing a visual guide to enhance understanding.