Is there a best approach to deploy an architecture to send SMS using a Microservice model?

后端 未结 2 2038
独厮守ぢ
独厮守ぢ 2021-02-02 04:42

We have a service within a Backend class, the service looks like:

// Setup AWS SNS
AWS.config.update({
    region: \'eu-west-1\',
    accessKeyI         


        
相关标签:
2条回答
  • 2021-02-02 05:15

    If your use case is to deliver single sms to individuals then you don't need to create a topic and delete it afterwards. It's possible to simply send one sms with the following code.

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

    the api endpoint/lambda receives the following body

    {
    "message": "hey ho I am the sms message.",
    "messageType": "Transactional", //or "Promotional"
    "messageSender": "Your Brand",
    "phone":"+436640339333"
    }
    
    0 讨论(0)
  • 2021-02-02 05:20

    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.

    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


    Simple view


    Explanation

    We are going to describe the process explaining step by step of the flow to deliver a SMS.

    1. A source needs to send a message to a specific telephone number, so the caller execute a POST request (/delivermessage) with the following payload to the API Gateway endpoing

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

    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:

      • Creates a SNS topic.
      • Creates a subscription using the received telephone number.
      • Subscribes it to the topic.
      • Publishes the message through that subscription.
      • Removes subscription.
      • Removes topic.
      • Returns back a success response to the caller:

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

    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 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 template

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

    For downloading click here

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

    Received SMS in my phone executing a request to the API Gateway endpoint

    Hope it helps to anybody who needs to deploy a microservice to send SMS.

    0 讨论(0)
提交回复
热议问题