SNS to AppSync subscription
How to send a subscription event to clients for an SNS message
SNS is a pub-sub service that can fan-out notifications in AWS. Many services can publish messages to it, and you can add implement logic in your own applications to do the same. Then SNS manages subscriptions and notifies the interested parties of these events.
In a different part of a cloud-based architecture, clients can subscribe to the real-time channel AppSync provides. Then when something interesting happens in the API, they can get an event via that channel.
In this article, we'll look into how to integrate the two worlds: whenever a message is published to an SNS topic, AppSync will send a notification to interested clients.
Overview
Let's start with the basics! There is an SNS topic that some process publishes messages. This can be EC2 lifecycle events from EventBridge, billing data from a separate system, DynamoDB changes via streams, or anything else.
Then on the other end, AppSync allows clients to subscribe via GraphQL requests. A subscription event is triggered by a GraphQL mutation, so we need one for that.
type Mutation {
receivedNotification(message: String!): String
}
type Subscription {
message: String
@aws_subscribe(mutations: ["receivedNotification"])
}
The SNS message needs to call this mutation to trigger the subscription event. Since that is not possible directly, we'll implement a Lambda function as a middleman.
Implementation
First, trigger the Lambda from the SNS topic:
resource "aws_lambda_permission" "sns" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda.arn
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.topic.arn
}
resource "aws_sns_topic_subscription" "lambda" {
topic_arn = aws_sns_topic.topic.arn
protocol = "lambda"
endpoint = aws_lambda_function.lambda.arn
}
Also, define the Lambda function:
resource "aws_lambda_function" "lambda" {
function_name = "${random_id.id.hex}-function"
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
environment {
variables = {
APIURL = aws_appsync_graphql_api.appsync.uris["GRAPHQL"]
apiRegion = data.aws_arn.appsync.region
NODE_NO_WARNINGS = "1"
}
}
timeout = 30
handler = "index.handler"
runtime = "nodejs18.x"
role = aws_iam_role.lambda_exec.arn
}
The function needs to know the AppSync API and also its region.
Then the function code reads the records it receives and calls the AppSync API:
export const handler = async (event) => {
const {APIURL, apiRegion} = process.env;
return Promise.all(event.Records.map(async (record) => {
const message = record.Sns.Message;
const body = {
query: `mutation trigger($message: String!) {
receivedNotification(message: $message)
}`,
operationName: "trigger",
variables: {message},
authMode: "AWS_IAM",
};
// ...sign and call the API
});
};
Testing
To test the implementation, first subscribe with a client to the AppSync subscription:
Second, publish a message to the SNS topic:
An event is received via the AppSync WebSocket channel: