これは、なにをしたくて書いたもの?
今まで試してきたServerless Frameworkを使った例では、ずっとAmazon API Gatewayを使ったものだったのですが、別のものでも試して
みようかなと思いまして。
Amazon SNS
Amazon SNS(Amazon Simple Notification Service)は、メッセージの配信者(publisher)からメッセージの受信者(subscriber)への
メッセージ配信を行うAWSのサービスです。
Amazon SNS とは - Amazon Simple Notification Service
A2A(Application to Application: Amazon Kinesis Data Firehose、AWS Lambda関数、Amazon SQSキューなど)のメッセージ配信、
Application to Person(モバイルアプリケーション、携帯電話番号、メールなど)の通知、標準トピックとFIFOトピックなどの特徴が
あります。
特徴と機能 - Amazon Simple Notification Service
今回は単純にトピックを作成して
Amazon SNS トピックを作成する - Amazon Simple Notification Service
AWS Lambda関数でサブスクライブするようにします。
Amazon SNS トピックへサブスクライブする - Amazon Simple Notification Service
この部分は、Serverless Frameworkに担ってもらうわけですが。
というわけで、Amazon SNSトピックをサブスクライブする簡単なAWS Lambda関数を、Serverless Frameworkを使って実現してみたいと
思います。環境はLocalStackを使い、AWS Lambda関数はNode.jsとTypeScriptを使って作成します。
環境
今回の環境は、こちら。
$ python3 -V Python 3.8.10 $ localstack --version 1.3.0
LocalStackを起動。
$ LAMBDA_EXECUTOR=docker-reuse localstack start
Node.js。
$ node --version v16.18.1 $ npm --version 8.19.2
$ awslocal --version aws-cli/2.9.4 Python/3.9.11 Linux/5.4.0-135-generic exe/x86_64.ubuntu.20 prompt/off
Serverless Frameworkのサービスを作成する
まずは、Serverless Frameworkのサービスを作成します。
$ npm init -y
Serverless Frameworkのインストール。
$ npm i -D serverless $ npx serverless --version Framework Core: 3.25.1 (local) Plugin: 6.2.2 SDK: 4.3.2
aws-nodejs
テンプレートを使って、サービスを作成。
$ npx serverless create --template aws-nodejs
Serverless Framework Commands - AWS Lambda - Create
あとはTypeScriptやServerless Frameworkのプラグインなどをインストール。
$ npm i -D typescript $ npm i -D @types/node@v16 $ npm i -D prettier $ npm i -D @types/aws-lambda $ npm i -D serverless-esbuild esbuild $ npm i -D serverless-localstack
package.json
のscripts
、
"scripts": { "typecheck": "tsc --project .", "typecheck:watch": "tsc --project . --watch", "format": "prettier --write ./**/*.{js,ts}" },
依存関係はそれぞれこんな感じに。
"devDependencies": { "@types/aws-lambda": "^8.10.109", "@types/node": "^16.18.4", "esbuild": "^0.15.17", "prettier": "^2.8.0", "serverless": "^3.25.1", "serverless-esbuild": "^1.33.2", "serverless-localstack": "^1.0.1", "typescript": "^4.9.3" }
設定ファイル。
tsconfig.json
{ "compilerOptions": { "target": "esnext", "module": "commonjs", "moduleResolution": "node", "lib": ["esnext"], "noEmit": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, "noPropertyAccessFromIndexSignature": true, "esModuleInterop": true }, "include": ["./**/*.ts"], "exclude": [ "node_modules/**/*", ".serverless/**/*" ] }
.prettierrc.json
{ "singleQuote": true, "printWidth": 120 }
デフォルトで作成されるAWS Lambda関数のソースコードは、削除しておきます。
$ rm handler.js
Amazon SNSトピックをサブスクライブするAWS Lambda関数を作成する
では、Amazon SNSトピックをサブスクライブするAWS Lambda関数を作成しましょう。
内容はログ出力程度で、こんな感じに作成しました。
sns/handler.ts
import { SNSEvent } from 'aws-lambda'; export const receiver = async (event: SNSEvent): Promise<void> => { for (const record of event.Records) { const topicArn = record.Sns.TopicArn; const messageId = record.Sns.MessageId; const message = record.Sns.Message; console.log(`topicArn = ${topicArn}, messageId = ${messageId}, message = ${message}`); } };
次に、Serverless Frameworkの設定を行います。
serverless.yml
service: serverless-sns frameworkVersion: '3' provider: name: aws runtime: nodejs16.x stage: dev region: us-east-1 package: individually: true functions: snsEventReceiver: handler: sns/handler.receiver events: - sns: arn: !Ref MyTopic topicName: my-topic resources: Resources: MyTopic: Type: AWS::SNS::Topic Properties: TopicName: my-topic custom: esbuild: bundle: true target: node16 platform: node plugins: - serverless-esbuild - serverless-localstack
今回は、Serverless FrameworkにAmazon SNSトピックを作成してもらうことにします。
とすると、実は以下の内容だけで実現できたりするんですよね。
functions: snsEventReceiver: handler: sns/handler.receiver events: - sns: my-topic
これで、デプロイ時にServerless Frameworkがsns
で指定したトピック名でAmazon SNSトピックを作成してくれます。
今回は、明示的にAmazon SNSのリソース定義まで行ってみました。
functions: snsEventReceiver: handler: sns/handler.receiver events: - sns: arn: !Ref MyTopic topicName: my-topic resources: Resources: MyTopic: Type: AWS::SNS::Topic Properties: TopicName: my-topic
こちらを参考に。
Serverless Framework - AWS Lambda Events - SNS
トピック名については、GetAtt
関数を使ってこんな感じで参照しようかなと思ったのですが、
functions: snsEventReceiver: handler: sns/handler.receiver events: - sns: arn: !Ref MyTopic topicName: !GetAtt MyTopic.TopicName resources: Resources: MyTopic: Type: AWS::SNS::Topic Properties: TopicName: my-topic
これはうまくいかなかったので、諦めました…。
Error: TypeError: name.replace is not a function at Object.normalizeNameToAlphaNumericOnly (/path/to/node_modules/serverless/lib/plugins/aws/lib/naming.js:32:36) at Object.normalizeTopicName (/path/to/node_modules/serverless/lib/plugins/aws/lib/naming.js:359:17) at Object.getLambdaSnsSubscriptionLogicalId (/path/to/node_modules/serverless/lib/plugins/aws/lib/naming.js:505:82) at /path/to/node_modules/serverless/lib/plugins/aws/package/compile/events/sns.js:185:64 at Array.forEach (<anonymous>) at /path/to/node_modules/serverless/lib/plugins/aws/package/compile/events/sns.js:63:28 at Array.forEach (<anonymous>) at AwsCompileSNSEvents.compileSNSEvents (/path/to/node_modules/serverless/lib/plugins/aws/package/compile/events/sns.js:59:47) at PluginManager.runHooks (/path/to/node_modules/serverless/lib/classes/plugin-manager.js:530:15) at async PluginManager.invoke (/path/to/node_modules/serverless/lib/classes/plugin-manager.js:564:9) at async PluginManager.spawn (/path/to/node_modules/serverless/lib/classes/plugin-manager.js:585:5) at async before:deploy:deploy (/path/to/node_modules/serverless/lib/plugins/deploy.js:40:11) at async PluginManager.runHooks (/path/to/node_modules/serverless/lib/classes/plugin-manager.js:530:9) at async PluginManager.invoke (/path/to/node_modules/serverless/lib/classes/plugin-manager.js:563:9) at async PluginManager.run (/path/to/node_modules/serverless/lib/classes/plugin-manager.js:604:7) at async Serverless.run (/path/to/node_modules/serverless/lib/serverless.js:170:5) at async /path/to/node_modules/serverless/scripts/serverless.js:787:9 1 deprecation found: run 'serverless doctor' for more details
では、デプロイしてみます。
$ npx serverless deploy
うまくいったようです。
Using serverless-localstack Deploying serverless-sns to stage dev (us-east-1) Skipping template validation: Unsupported in Localstack ✔ Service deployed to stack serverless-sns-dev (13s) functions: snsEventReceiver: serverless-sns-dev-snsEventReceiver (744 B) Need a better logging experience than CloudWatch? Try our Dev Mode in console: run "serverless --console"
$ awslocal sns list-topics { "Topics": [ { "TopicArn": "arn:aws:sns:us-east-1:000000000000:my-topic" } ] }
メッセージを送信してみます。
$ awslocal sns publish --topic-arn arn:aws:sns:us-east-1:000000000000:my-topic --message 'Hello SNS!!'
こちらのページにはAWS CLIを使ったメッセージ送信方法は書いていませんでしたが、publis
でできるみたいです…。
Amazon SNS メッセージの発行 - Amazon Simple Notification Service
Dockerコンテナのログを見て確認。
$ docker container logs -f localstack_main_lambda_arn_aws_lambda_us-east-1_000000000000_function_serverless-sns-dev-snsEventReceiver
OKですね。
START RequestId: d0975886-d370-137c-30dd-fd2889ee1272 Version: $LATEST 2022-12-04T14:51:46.534Z d0975886-d370-137c-30dd-fd2889ee1272 INFO topicArn = arn:aws:sns:us-east-1:000000000000:my-topic, messageId = ccaed308-8afd-42cd-bccf-c72388d68528, message = Hello SNS!! END RequestId: d0975886-d370-137c-30dd-fd2889ee1272 REPORT RequestId: d0975886-d370-137c-30dd-fd2889ee1272 Duration: 3.92 ms Billed Duration: 4 ms Memory Size: 1536 MB Max Memory Used: 44 MB
まとめ
Serverless Framework+LocalStackで、Amazon SNSトピックをサブスクライブするAWS Lambda関数を作ってみました。
AWS CloudFormationの関数を使おうとしてちょっとハマりましたが、それ以外は特に困らなかったですね。