昨天釐清需求和設計完架構之後,就可以拿著需求請 AI 幫忙寫 CloudFormation 的 template ,接著透過 CloudFormation 把 infra 和程式碼部署到 AWS 即可。下面是 CloudFormation template的範例,部署的時候,會需要先填三個預設的 email 當範例,等到 infra 部署完成,記得去 email 點驗證連結通過驗證。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'API Gateway + Lambda + DynamoDB + SNS: Store Article and Notify on Success (by hjoru)'
Parameters:
Email1:
Type: String
Description: The first notification email address
Email2:
Type: String
Description: The second notification email address
Email3:
Type: String
Description: The third notification email address
Resources:
ArticlesTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: Articles
AttributeDefinitions:
- AttributeName: ArticleId
AttributeType: S
KeySchema:
- AttributeName: ArticleId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
ArticleNotificationTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: DirectPublishTopic
DisplayName: BlogNotifications
EmailSubscription1:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref ArticleNotificationTopic
Protocol: email
Endpoint: !Ref Email1
EmailSubscription2:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref ArticleNotificationTopic
Protocol: email
Endpoint: !Ref Email2
EmailSubscription3:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref ArticleNotificationTopic
Protocol: email
Endpoint: !Ref Email3
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${AWS::StackName}-LambdaRole'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: DynamoDBSNSAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:PutItem
Resource: !GetAtt ArticlesTable.Arn
- Effect: Allow
Action:
- sns:Publish
Resource: !Ref ArticleNotificationTopic
StoreAndNotifyLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: StoreArticleAndNotify
Runtime: python3.9
Handler: index.handler
Timeout: 20
MemorySize: 128
Environment:
Variables:
TOPIC_ARN: !Ref ArticleNotificationTopic
TABLE_NAME: !Ref ArticlesTable
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import json
import boto3
import uuid
from datetime import datetime
import os
dynamodb = boto3.resource('dynamodb')
sns = boto3.client('sns')
table_name = os.environ['TABLE_NAME']
topic_arn = os.environ['TOPIC_ARN']
table = dynamodb.Table(table_name)
def handler(event, context):
try:
# Try to parse API Gateway POST body or direct invoke
if 'body' in event and event['body']:
try:
body = json.loads(event['body'])
except Exception:
body = event['body']
else:
body = event
# Required fields, others are optional
title = body.get('title', '')
content = body.get('content', '')
author = body.get('author', '')
summary = body.get('summary', '')
url = body.get('url', '')
columnId = body.get('columnId', '')
article_id = str(uuid.uuid4())
now = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
item = {
'ArticleId': article_id,
'Title': title,
'Content': content,
'Author': author,
'Summary': summary,
'Url': url,
'ColumnId': columnId,
'CreatedAt': now
}
# 1. Write to DynamoDB
table.put_item(Item=item)
# 2. Notify via SNS only if DynamoDB write is successful
subject = f"New Article: {title or '(No Title)'}"
html_message = f"""
<html>
<head><title>{title}</title></head>
<body>
<h2>{title}</h2>
<p><b>Author:</b> {author}</p>
<p>{summary}</p>
<a href="{url}" target="_blank">Read more</a>
<p><small>Published at: {now}</small></p>
</body>
</html>
"""
# SNS Email only supports plain text & limited subject, so send both
message = f"Title: {title}\nAuthor: {author}\nSummary: {summary}\nURL: {url}\nCreated At: {now}"
sns.publish(
TopicArn=topic_arn,
Message=message,
Subject=subject
)
return {
'statusCode': 200,
'body': json.dumps({
'message': 'Article stored and notification sent',
'articleId': article_id
})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
# API Gateway (REST API for Lambda)
ArticleApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: ArticleApi
Description: 'API for storing articles and sending notifications'
ArticleApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt ArticleApi.RootResourceId
PathPart: article
RestApiId: !Ref ArticleApi
ArticleApiMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref ArticleApi
ResourceId: !Ref ArticleApiResource
HttpMethod: POST
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn}/invocations
- { LambdaArn: !GetAtt StoreAndNotifyLambda.Arn }
ArticleApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn: ArticleApiMethod
Properties:
RestApiId: !Ref ArticleApi
StageName: prod
LambdaInvokePermissionApiGateway:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt StoreAndNotifyLambda.Arn
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com
SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ArticleApi}/*/*
Outputs:
ApiEndpoint:
Description: "POST endpoint for storing articles"
Value: !Sub "https://${ArticleApi}.execute-api.${AWS::Region}.amazonaws.com/prod/article"
TableName:
Description: "DynamoDB Table Name"
Value: !Ref ArticlesTable
TopicArn:
Description: "SNS Topic ARN"
Value: !Ref ArticleNotificationTopic
LambdaFunction:
Description: "Lambda function for storing articles and sending notifications"
Value: !Ref StoreAndNotifyLambda
部署完之後如果要測試,可以先到 API Gateway 取得 API 連結。
接著使用 curl
或 Postman 打 API ,收到成功的 response 之後,就可以去 email 查看有沒有收到通知。
curl --location 'https://av1qag0me0.execute-api.us-east-1.amazonaws.com/prod/article' \
--header 'Content-Type: application/json' \
--data '{
"columnId": "tech-column-101",
"title": "DynamoDB 存文章實作",
"content": "這篇文章示範如何用 Lambda 存資料到 DynamoDB...",
"author": "hjoru",
"summary": "Lambda + DynamoDB 實戰",
"url": "https://yourblog.com/posts/dynamodb-integration"
}'
email 收到信件就可以確認整個流程沒問題!