是否有最佳方法来部署体系结构以使用微服务模型发送SMS? [英] Is there a best approach to deploy an architecture to send SMS using a Microservice model?

查看:57
本文介绍了是否有最佳方法来部署体系结构以使用微服务模型发送SMS?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在Backend类中提供了一项服务,该服务如下所示:

// 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'
    }
};

如果需要发送短信,我们只需致电此服务即可.

If we need to send an SMS, we simply call this service.

以下是坏处,我们知道:

  • 我们在EC2中使用密钥.但是,我们正在努力设置一个对实例具有特定权限的角色.

  • We're using the Secret keys within the EC2. However, we're working on that for setting a role with the specific permissions to the instances.

想象一下,我们需要修改发送短信的方式,我们只需要为应用程序的一小部分重新部署整个应用程序即可.

Imagine we need to modify the way we are sending SMS, we would have to re-deploy the entire app just for that tiny part of our application.

最糟糕的是,假设我们在AutoScaling上拥有我们的应用程序.我们只需要删除所有实例即可更新应用程序的一小部分.

Worst, imagine we have our application on AutoScaling. We would have to drop off all the instances just to update that tiny part of our application.

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

Another problem is, what if we have to use that service in other applications? The current approach leads to duplicate a service among applications.

最后,如何记录,监视等等.

Last, how to logging, monitoring, Etc.

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

推荐答案

经过数小时的头脑风暴,我们决定使用AWS的四项基本服务

  • AWS API网关
  • AWS Lambda函数
  • AWS SNS(简单通知服务)
  • AWS Cloudwatch 用于监视和记录.
  • After hours of brainstorming we decided to use four basic services from AWS

    • AWS API Gateway
    • AWS Lambda function
    • AWS SNS (Simple Notification Service)
    • AWS Cloudwatch for monitoring and logging.
    • 此体系结构允许您提供一个Restful端点,该端点将消息传递到特定的接收者.可以从您的应用程序,设备应用程序等的不同部分执行此微服务,因此它不仅限于一个后端目的.

      This architecture allows you to provide a Restful Endpoint which delivers a message to a specific receiver. This microservice could be executed from different parts of your application, device apps, Etc., so isn't tied to only one Backend purpose.

      体系结构如下

      详细视图

      The architecture looks as follow

      Detailed view

      我们将逐步描述交付SMS的流程.

      1. 源需要将消息发送到特定的电话号码,因此调用方将通过以下有效负载执行POST请求(/传递消息)到API网关端.


      {
         "target": "554542121245",
         "type": "sms",
         "message": "Hello World!",
         "region": "us-east-1"
      }
      


      1. API网关会验证API以授予访问权限,并将接收到的有效负载发送到Lambda函数.
      2. Lambda函数验证收到的有效负载并执行以下操作:

      1. The API Gateway validates the API to grant access and send the received payload to the Lambda function.
      2. The Lambda function validates the received payload and execute the following:

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


      {
          "status": 200,
          "message": "The message has been sent!"
      }
      


      1. API网关评估响应并将响应发送回调用方.
        • API网关具有智能功能,可以检查从Lambda函数发送了什么样的响应.
        • 对于响应以412开头表示前提条件失败.
        • 对于以500开头的响应表示内部服务器错误.
      1. The API Gateway evaluates the response and send back the response to the caller.
        • The API Gateway has intelligence to check what kind of response was sent from the Lambda function.
        • For response starts with 412 means Precondition Failed.
        • For response starts with 500 means Internal server error.


      Lambda代码(NodeJs)


      Lambda Code (NodeJs)

      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}`;
         }
      };
      


      Cloudformation模板

      此cloudformation模板描述了整套服务,API网关,Lambda函数,角色,权限,API使用计划,API密钥等.

      This cloudformation template describes the whole set of services, API Gateway, Lambda function, Roles, Permissions, Usage plans for the API, API Key, Etc.

      要下载,请点击 此处

      {
          "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"
                                  },
                                  "/*"
                              ]
                          ]
                      }
                  }
              }
          }
      }
      

      我手机中收到的短信正在执行向API网关端点的请求

      希望它对需要部署微服务以发送SMS的所有人有所帮助.

      这篇关于是否有最佳方法来部署体系结构以使用微服务模型发送SMS?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆