Node.js 是否有一种最佳的方法来部署一个使用微服务模型发送SMS的体系结构?

Node.js 是否有一种最佳的方法来部署一个使用微服务模型发送SMS的体系结构?,node.js,amazon-web-services,aws-lambda,aws-api-gateway,amazon-sns,Node.js,Amazon Web Services,Aws Lambda,Aws Api Gateway,Amazon Sns,我们在后端类中有一个服务,该服务看起来像: // Setup AWS SNS AWS.config.update({ region: 'eu-west-1', accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY }); var sns = new AWS.SNS(); var params = { Message: "SMS

我们在后端类中有一个服务,该服务看起来像:

// Setup AWS SNS
AWS.config.update({
    region: 'eu-west-1',
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
});
var sns = new AWS.SNS();

var params = {
    Message: "SMS message test",
    MessageStructure: 'string',
    PhoneNumber: '0045xxxxxxxx',
    Subject: 'Alarm',
    MessageAttributes :{
        'AWS.SNS.SMS.SenderID': {
            'DataType': 'String',
            'StringValue': 'MySender'
        },
        'AWS.SNS.SMS.SMSType': 'Transactional'
    }
};
如果我们需要发送短信,我们只需呼叫此服务

我们知道,这里不好的地方如下:

// Setup AWS SNS
AWS.config.update({
    region: 'eu-west-1',
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
});
var sns = new AWS.SNS();

var params = {
    Message: "SMS message test",
    MessageStructure: 'string',
    PhoneNumber: '0045xxxxxxxx',
    Subject: 'Alarm',
    MessageAttributes :{
        'AWS.SNS.SMS.SenderID': {
            'DataType': 'String',
            'StringValue': 'MySender'
        },
        'AWS.SNS.SMS.SMSType': 'Transactional'
    }
};
  • 我们正在使用EC2中的密钥。不过,我们正在进行这项工作,以设置具有实例特定权限的角色

  • 假设我们需要修改发送短信的方式,我们将不得不重新部署整个应用程序,只为应用程序的一小部分

  • 最糟糕的是,假设我们的应用程序是自动缩放的。我们将不得不放弃所有的实例,只是为了更新应用程序的那一小部分

  • 另一个问题是,如果我们必须在其他应用程序中使用该服务,该怎么办?当前的方法导致在应用程序之间复制服务

  • 最后,如何进行日志记录、监控等

我们认为有更好的方法来避免此类问题,因此您可以看到我们避免上述问题的方法。

经过数小时的头脑风暴,我们决定使用AWS的四项基本服务
  • 用于监视和记录
此体系结构允许您提供一个Restful端点,该端点将消息传递给特定的接收者。这项微服务可以从应用程序、设备应用程序等的不同部分执行,因此不会仅限于一个后端用途

##架构如下所示 ###详细视图


###简单视图


#解释

我们将描述一步一步解释发送SMS流程的过程

  • 源需要向特定的电话号码发送消息,因此调用者使用以下有效负载执行POST请求(/delivermessage)到API网关端点


  • API网关验证API以授予访问权限,并将收到的有效负载发送给Lambda函数

  • Lambda函数验证接收的有效负载并执行以下操作:

    • 创建一个SNS主题
    • 使用收到的电话号码创建订阅
    • 订阅该主题
    • 通过该订阅发布消息
    • 删除订阅
    • 删除主题
    • 将成功响应返回给调用方:


  • API网关评估响应并将响应发送回调用方。
    • API网关具有检查Lambda函数发送的响应类型的智能
    • 对于以412开头的响应,表示前置条件失败
    • 对于以500开头的响应,表示内部服务器错误

  • Lambda代码(NodeJs)
    云形成模板 此cloudformation模板描述了整套服务、API网关、Lambda函数、角色、权限、API的使用计划、API密钥等

    要下载,请单击

    #在我的手机中接收到短信,执行对API网关端点的请求

    更新-2021
    SNS js sdk提供了一种直接发送短信的方式,无需创建主题。

    如果您的用例是向个人发送单个短信,那么您不需要创建主题并在之后删除它。可以简单地用以下代码发送一条短信

    let AWS = require('aws-sdk');
    const sns = new AWS.SNS();
    exports.handler = function (event, context, callback) {
        var params = {
      Message: event.message, //  your message you would like to send
            MessageAttributes: {
                'AWS.SNS.SMS.SMSType': {
                    DataType: 'String',
                    StringValue: event.messageType // the smsType "Transactional" or "Promotional"
                },
                'AWS.SNS.SMS.SenderID': {
                    DataType: 'String',
                    StringValue: event.messageSender // your senderId - the message that will show up as the sender on the receiving phone
                },
            },
      PhoneNumber: event.phone // the phone number of the receiver 
    };
    
    sns.publish(params, function (err, data) {
            callback(null, {err: err, data: data});
            if (err) {
                console.log(err);
                context.fail(err);
            } else {
                console.log("Send sms successful to user:", event.phone);
                context.succeed(event);
                return;
            }
        });
    };
    
    api端点/lambda接收以下主体

    {
    "message": "hey ho I am the sms message.",
    "messageType": "Transactional", //or "Promotional"
    "messageSender": "Your Brand",
    "phone":"+436640339333"
    }
    

    是否每个请求都会创建一个SNS主题?是的,它会立即创建和删除。我可以在这里看到两个问题,它不是删除主题,所以很多主题创建了其他问题,我们需要手动批准主题,SNS SMS服务仅在少数区域可用,因此其他区域由于手动批准主题而出现无效协议错误和支持区域超时
    {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "This template deploys the necessary resources for sending MSG through a API-Gateway endpoint, Lambda function and SNS service.",
        "Metadata": {
            "License": {
                "Description": "MIT license - Copyright (c) 2017"
            }
        },
        "Resources": {
            "LambdaRole": {
                "Type": "AWS::IAM::Role",
                "Properties": {
                    "AssumeRolePolicyDocument": {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Principal": {
                                    "Service": [
                                        "lambda.amazonaws.com"
                                    ]
                                },
                                "Action": [
                                    "sts:AssumeRole"
                                ]
                            }
                        ]
                    },
                    "Policies": [
                        {
                            "PolicyName": "LambdaSnsNotification",
                            "PolicyDocument": {
                                "Version": "2012-10-17",
                                "Statement": [
                                    {
                                        "Sid": "AllowSnsActions",
                                        "Effect": "Allow",
                                        "Action": [
                                            "sns:Publish",
                                            "sns:Subscribe",
                                            "sns:Unsubscribe",
                                            "sns:DeleteTopic",
                                            "sns:CreateTopic"
                                        ],
                                        "Resource": "*"
                                    }
                                ]
                            }
                        }
                    ]
                }
            },
            "LambdaFunctionMessageSNSTopic": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Description": "Send message to a specific topic that will deliver MSG to a receiver.",
                    "Handler": "index.handler",
                    "MemorySize": 128,
                    "Role": {
                        "Fn::GetAtt": [
                            "LambdaRole",
                            "Arn"
                        ]
                    },
                    "Runtime": "nodejs6.10",
                    "Timeout": 60,
                    "Environment": {
                        "Variables": {
                            "sns_topic_arn": ""
                        }
                    },
                    "Code": {
                        "ZipFile": {
                            "Fn::Join": [
                                "\n",
                                [
                                    "var AWS = require('aws-sdk');",
                                    "",
                                    "/**",
                                    " * Entry function for this",
                                    " * Lambda.",
                                    " * ",
                                    " * This function delivers a message ",
                                    " * to a specific number.",
                                    " * ",
                                    " * First approach will only handle ",
                                    " * delivery type sms.",
                                    " */",
                                    "exports.handler = (event, context, callback) => {",
                                    "    console.log(JSON.stringify(event));",
                                    "",
                                    "    if (event.type === undefined || event.type === null || event.type === '' || event.type.trim() === '') {",
                                    "        callback(get_response_message('Type of delivery is required.'), 412);",
                                    "        return;",
                                    "    }",
                                    "   ",
                                    "    if (event.type.trim() !== 'sms') {",
                                    "        callback(get_response_message('The available delivery type is \'sms\'.', 412));",
                                    "        return;",
                                    "    }",
                                    "",
                                    "    if (event.type.trim() === 'sms' && (event.target === '' || isNaN(event.target))) {",
                                    "        callback(get_response_message('The target must be a number.', 412));",
                                    "        return;",
                                    "    }",
                                    "",
                                    "    deliver(event.target, event.message, event.region, callback);",
                                    "};",
                                    "",
                                    "/**",
                                    " * This function delivers a",
                                    " * message to a specific number.",
                                    " * ",
                                    " * The function will create a topic",
                                    " * from scratch to avoid any",
                                    " * clash among subscriptions.",
                                    " * ",
                                    " * @param number in context.",
                                    " * @param message that will be sent.",
                                    " * @param region in context.",
                                    " * @param cb a callback function to ",
                                    " *           return a response to the ",
                                    " *           caller of this service.",
                                    " */",
                                    "var deliver = (number, message, region, cb) => {",
                                    "   var sns = new AWS.SNS({region: region});",
                                    "   console.log(`${number} - ${region} - ${Date.now()}`);",
                                    "   var params = { Name: `${number}_${region}_${Date.now()}` };",
                                    "",
                                    "   sns.createTopic(params, function(err, tdata) {",
                                    "     if (err) {",
                                    "         console.log(err, err.stack);",
                                    "         cb(get_response_message(err, 500));",
                                    "     } else {",
                                    "         console.log(tdata.TopicArn);",
                                    "         sns.subscribe({",
                                    "           Protocol: 'sms',",
                                    "           TopicArn: tdata.TopicArn,",
                                    "           Endpoint: number",
                                    "       }, function(error, data) {",
                                    "            if (error) {",
                                    "               //Rollback to the previous created services.",
                                    "                console.log(error, error.stack);",
                                    "               params = { TopicArn: tdata.TopicArn};",
                                    "               sns.deleteTopic(params, function() { cb(get_response_message(error, 500)); });",
                                    "",
                                    "               return;",
                                    "            }",
                                    "",
                                    "            console.log('subscribe data', data);",
                                    "            var SubscriptionArn = data.SubscriptionArn;",
                                    "",
                                    "            params = { TargetArn: tdata.TopicArn, Message: message, Subject: 'dummy' };",
                                    "            sns.publish(params, function(err_publish, data) {",
                                    "               if (err_publish) {",
                                    "                    console.log(err_publish, err_publish.stack);",
                                    "                   //Rollback to the previous created services.",
                                    "                   params = { TopicArn: tdata.TopicArn};",
                                    "                   sns.deleteTopic(params, function() {",
                                    "                       params = {SubscriptionArn: SubscriptionArn};",
                                    "                       sns.unsubscribe(params, function() { cb(get_response_message(err_publish, 500)); });",
                                    "                   });",
                                    "",
                                    "                    return;",
                                    "               } else console.log('Sent message:', data.MessageId);",
                                    "",
                                    "               params = { SubscriptionArn: SubscriptionArn };",
                                    "               sns.unsubscribe(params, function(err, data) {",
                                    "                  if (err) console.log('err when unsubscribe', err);",
                                    "",
                                    "                  params = { TopicArn: tdata.TopicArn };",
                                    "                  sns.deleteTopic(params, function(rterr, rtdata) {",
                                    "                     if (rterr) {",
                                    "                        console.log(rterr, rterr.stack);",
                                    "                        cb(get_response_message(rterr, 500));",
                                    "                     } else {",
                                    "                        console.log(rtdata);",
                                    "                        cb(null, get_response_message('Message has been sent!', 200));",
                                    "                     }",
                                    "                  });",
                                    "               });",
                                    "           });",
                                    "         });",
                                    "      }",
                                    "   });",
                                    "};",
                                    "",
                                    "/**",
                                    " * This function returns the response",
                                    " * message that will be sent to the ",
                                    " * caller of this service.",
                                    " */",
                                    "var get_response_message = (msg, status) => {",
                                    "   if (status == 200) {",
                                    "      return `{'status': ${status}, 'message': ${msg}}`;",
                                    "   } else {",
                                    "      return `${status} - ${msg}`;",
                                    "   }",
                                    "};"
                                ]
                            ]
                        }
                    }
                }
            },
            "MSGGatewayRestApi": {
                "Type": "AWS::ApiGateway::RestApi",
                "Properties": {
                    "Name": "MSG RestApi",
                    "Description": "API used for sending MSG",
                    "FailOnWarnings": true
                }
            },
            "MSGGatewayRestApiUsagePlan": {
                "Type": "AWS::ApiGateway::UsagePlan",
                "Properties": {
                    "ApiStages": [
                        {
                            "ApiId": {
                                "Ref": "MSGGatewayRestApi"
                            },
                            "Stage": {
                                "Ref": "MSGGatewayRestApiStage"
                            }
                        }
                    ],
                    "Description": "Usage plan for stage v1",
                    "Quota": {
                        "Limit": 5000,
                        "Period": "MONTH"
                    },
                    "Throttle": {
                        "BurstLimit": 200,
                        "RateLimit": 100
                    },
                    "UsagePlanName": "Usage_plan_for_stage_v1"
                }
            },
            "RestApiUsagePlanKey": {
                "Type": "AWS::ApiGateway::UsagePlanKey",
                "Properties": {
                    "KeyId": {
                        "Ref": "MSGApiKey"
                    },
                    "KeyType": "API_KEY",
                    "UsagePlanId": {
                        "Ref": "MSGGatewayRestApiUsagePlan"
                    }
                }
            },
            "MSGApiKey": {
                "Type": "AWS::ApiGateway::ApiKey",
                "Properties": {
                    "Name": "MSGApiKey",
                    "Description": "CloudFormation API Key v1",
                    "Enabled": "true",
                    "StageKeys": [
                        {
                            "RestApiId": {
                                "Ref": "MSGGatewayRestApi"
                            },
                            "StageName": {
                                "Ref": "MSGGatewayRestApiStage"
                            }
                        }
                    ]
                }
            },
            "MSGGatewayRestApiStage": {
                "DependsOn": [
                    "ApiGatewayAccount"
                ],
                "Type": "AWS::ApiGateway::Stage",
                "Properties": {
                    "DeploymentId": {
                        "Ref": "RestAPIDeployment"
                    },
                    "MethodSettings": [
                        {
                            "DataTraceEnabled": true,
                            "HttpMethod": "*",
                            "LoggingLevel": "INFO",
                            "ResourcePath": "/*"
                        }
                    ],
                    "RestApiId": {
                        "Ref": "MSGGatewayRestApi"
                    },
                    "StageName": "v1"
                }
            },
            "ApiGatewayCloudWatchLogsRole": {
                "Type": "AWS::IAM::Role",
                "Properties": {
                    "AssumeRolePolicyDocument": {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Principal": {
                                    "Service": [
                                        "apigateway.amazonaws.com"
                                    ]
                                },
                                "Action": [
                                    "sts:AssumeRole"
                                ]
                            }
                        ]
                    },
                    "Policies": [
                        {
                            "PolicyName": "ApiGatewayLogsPolicy",
                            "PolicyDocument": {
                                "Version": "2012-10-17",
                                "Statement": [
                                    {
                                        "Effect": "Allow",
                                        "Action": [
                                            "logs:CreateLogGroup",
                                            "logs:CreateLogStream",
                                            "logs:DescribeLogGroups",
                                            "logs:DescribeLogStreams",
                                            "logs:PutLogEvents",
                                            "logs:GetLogEvents",
                                            "logs:FilterLogEvents"
                                        ],
                                        "Resource": "*"
                                    }
                                ]
                            }
                        }
                    ]
                }
            },
            "ApiGatewayAccount": {
                "Type": "AWS::ApiGateway::Account",
                "Properties": {
                    "CloudWatchRoleArn": {
                        "Fn::GetAtt": [
                            "ApiGatewayCloudWatchLogsRole",
                            "Arn"
                        ]
                    }
                }
            },
            "RestAPIDeployment": {
                "Type": "AWS::ApiGateway::Deployment",
                "DependsOn": [
                    "MSGGatewayRequest"
                ],
                "Properties": {
                    "RestApiId": {
                        "Ref": "MSGGatewayRestApi"
                    },
                    "StageName": "DummyStage"
                }
            },
            "ApiGatewayMSGResource": {
                "Type": "AWS::ApiGateway::Resource",
                "Properties": {
                    "RestApiId": {
                        "Ref": "MSGGatewayRestApi"
                    },
                    "ParentId": {
                        "Fn::GetAtt": [
                            "MSGGatewayRestApi",
                            "RootResourceId"
                        ]
                    },
                    "PathPart": "delivermessage"
                }
            },
            "MSGGatewayRequest": {
                "DependsOn": "LambdaPermission",
                "Type": "AWS::ApiGateway::Method",
                "Properties": {
                    "ApiKeyRequired": true,
                    "AuthorizationType": "NONE",
                    "HttpMethod": "POST",
                    "Integration": {
                        "Type": "AWS",
                        "IntegrationHttpMethod": "POST",
                        "Uri": {
                            "Fn::Join": [
                                "",
                                [
                                    "arn:aws:apigateway:",
                                    {
                                        "Ref": "AWS::Region"
                                    },
                                    ":lambda:path/2015-03-31/functions/",
                                    {
                                        "Fn::GetAtt": [
                                            "LambdaFunctionMessageSNSTopic",
                                            "Arn"
                                        ]
                                    },
                                    "/invocations"
                                ]
                            ]
                        },
                        "IntegrationResponses": [
                            {
                                "StatusCode": 200
                            },
                            {
                                "SelectionPattern": "500.*",
                                "StatusCode": 500
                            },
                            {
                                "SelectionPattern": "412.*",
                                "StatusCode": 412
                            }
                        ],
                        "RequestTemplates": {
                            "application/json": ""
                        }
                    },
                    "RequestParameters": {
                    },
                    "ResourceId": {
                        "Ref": "ApiGatewayMSGResource"
                    },
                    "RestApiId": {
                        "Ref": "MSGGatewayRestApi"
                    },
                    "MethodResponses": [
                        {
                            "StatusCode": 200
                        },
                        {
                            "StatusCode": 500
                        },
                        {
                            "StatusCode": 412
                        }
                    ]
                }
            },
            "LambdaPermission": {
                "Type": "AWS::Lambda::Permission",
                "Properties": {
                    "Action": "lambda:invokeFunction",
                    "FunctionName": {
                        "Fn::GetAtt": [
                            "LambdaFunctionMessageSNSTopic",
                            "Arn"
                        ]
                    },
                    "Principal": "apigateway.amazonaws.com",
                    "SourceArn": {
                        "Fn::Join": [
                            "",
                            [
                                "arn:aws:execute-api:",
                                {
                                    "Ref": "AWS::Region"
                                },
                                ":",
                                {
                                    "Ref": "AWS::AccountId"
                                },
                                ":",
                                {
                                    "Ref": "MSGGatewayRestApi"
                                },
                                "/*"
                            ]
                        ]
                    }
                }
            }
        }
    }
    
    let AWS = require('aws-sdk');
    const sns = new AWS.SNS();
    exports.handler = function (event, context, callback) {
        var params = {
      Message: event.message, //  your message you would like to send
            MessageAttributes: {
                'AWS.SNS.SMS.SMSType': {
                    DataType: 'String',
                    StringValue: event.messageType // the smsType "Transactional" or "Promotional"
                },
                'AWS.SNS.SMS.SenderID': {
                    DataType: 'String',
                    StringValue: event.messageSender // your senderId - the message that will show up as the sender on the receiving phone
                },
            },
      PhoneNumber: event.phone // the phone number of the receiver 
    };
    
    sns.publish(params, function (err, data) {
            callback(null, {err: err, data: data});
            if (err) {
                console.log(err);
                context.fail(err);
            } else {
                console.log("Send sms successful to user:", event.phone);
                context.succeed(event);
                return;
            }
        });
    };
    
    {
    "message": "hey ho I am the sms message.",
    "messageType": "Transactional", //or "Promotional"
    "messageSender": "Your Brand",
    "phone":"+436640339333"
    }