AWSラムダとAWS APIゲートウェイを用いたサーバレスAPIの開発(その2 )


TL例のプロジェクトを持つリポジトリ博士はGitHub
これはAWS技術を持つServerless APIを構築する2部シリーズの最後の記事です.
最初の部分では、認証、リクエスト本体、ステータスコード、Corsおよびレスポンスヘッダーについて学びました.私たちは、ユーザーがサインアップすることができたように、APIゲートウェイ、ラムダと認知に接続されたAWS SAMプロジェクトをセットアップしました.
本稿では、サードパーティのインテグレーション、データストレージ、検索、ラムダ関数からのラムダ関数の呼び出しについて説明します.

画像アップロードの追加
画像のアップロードを開始します.次のように動作します.
  • 事前に署名されたS 3 URLをリクエストします
  • プリサインされたURLを介して直接S 3に画像をアップロードする
  • それで、我々は2つの新しいSAM/Cloudformation資源を必要とします.プリサインされたURLとS 3バケツを我々のイメージのために生成するラムダ関数.
    SAMテンプレートを更新しましょう
    AWSTemplateFormatVersion: "2010-09-09"
    Transform: "AWS::Serverless-2016-10-31"
    Description: "A example REST API build with serverless technology"
    
    Globals:
    
      Function:
        Runtime: nodejs8.10
        Handler: index.handler
        Timeout: 30 
        Tags:
          Application: Serverless API
    
    Resources:
    
      ServerlessApi:
        Type: AWS::Serverless::Api
        Properties:
          StageName: Prod
          Cors: "'*'"
          Auth:
            DefaultAuthorizer: CognitoAuthorizer
            Authorizers:
              CognitoAuthorizer:
                UserPoolArn: !GetAtt UserPool.Arn
          GatewayResponses:
            UNAUTHORIZED:
              StatusCode: 401
              ResponseParameters:
                Headers:
                  Access-Control-Expose-Headers: "'WWW-Authenticate'"
                  Access-Control-Allow-Origin: "'*'"
                  WWW-Authenticate: >-
                    'Bearer realm="admin"'
    
      # ============================== Auth ==============================
      AuthFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/auth/
          Environment:
            Variables:
              USER_POOL_ID: !Ref UserPool
              USER_POOL_CLIENT_ID: !Ref UserPoolClient
          Events:
            Signup:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /signup
                Method: POST
                Auth:
                  Authorizer: NONE
            Signin:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /signin
                Method: POST
                Auth:
                  Authorizer: NONE
    
      PreSignupFunction:
        Type: AWS::Serverless::Function
        Properties:
          InlineCode: |
            exports.handler = async event => {
              event.response = { autoConfirmUser: true };
              return event;
            };
    
      UserPool:
        Type: AWS::Cognito::UserPool
        Properties:
          UserPoolName: ApiUserPool
          LambdaConfig:
            PreSignUp: !GetAtt PreSignupFunction.Arn
          Policies:
            PasswordPolicy:
              MinimumLength: 6
    
      UserPoolClient:
        Type: AWS::Cognito::UserPoolClient
        Properties:
          UserPoolId: !Ref UserPool
          ClientName: ApiUserPoolClient
          GenerateSecret: no
    
      LambdaCognitoUserPoolExecutionPermission:
        Type: AWS::Lambda::Permission
        Properties: 
          Action: lambda:InvokeFunction
          FunctionName: !GetAtt PreSignupFunction.Arn
          Principal: cognito-idp.amazonaws.com
          SourceArn: !Sub 'arn:${AWS::Partition}:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${UserPool}'
    
      # ============================== Images ==============================
      ImageBucket:
        Type: AWS::S3::Bucket
        Properties: 
          AccessControl: PublicRead
          CorsConfiguration:
            CorsRules:
              - AllowedHeaders:
                  - "*"
                AllowedMethods:
                  - HEAD
                  - GET
                  - PUT
                  - POST
                AllowedOrigins:
                  - "*"
    
      ImageBucketPublicReadPolicy:
        Type: AWS::S3::BucketPolicy
        Properties:
          Bucket: !Ref ImageBucket
          PolicyDocument:
            Statement:
              - Action: s3:GetObject
                Effect: Allow
                Principal: "*"
                Resource: !Join ["", ["arn:aws:s3:::", !Ref "ImageBucket", "/*" ]]
    
      ImageFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/images/
          Policies:
            - AmazonS3FullAccess
          Environment:
            Variables:
              IMAGE_BUCKET_NAME: !Ref ImageBucket
          Events:
            CreateImage:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /images
                Method: POST
    
    Outputs:
    
      ApiUrl:
        Description: The target URL of the created API
        Value: !Sub "https://${ServerlessApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
        Export:
          Name: ApiUrl
    
    さて、私たちは3つの新しい資源に終わりました.我々はまたBucketPolicy それは私たちの新しいイメージのバケットの公共の読み取りを許可します.
    The ImagesFunction APIイベントがあるので、ポストリクエストを処理できます.この関数はS 3アクセスポリシーと環境変数を取得しますImageBucket .
    関数コードの新しいファイルを作成する必要がありますfunctions/images/index.js .
    const AWS = require("aws-sdk");
    
    exports.handler = async event => {
      const userName = event.requestContext.authorizer.claims["cognito:username"];
      const fileName = "" + Math.random() + Date.now() + "+" + userName;
      const { url, fields } = await createPresignedUploadCredentials(fileName);
      return {
        statusCode: 201,
        body: JSON.stringify({
          formConfig: {
            uploadUrl: url,
            formFields: fields
          }
        }),
        headers: { "Access-Control-Allow-Origin": "*" }
      };
    };
    
    const s3Client = new AWS.S3();
    const createPresignedUploadCredentials = fileName => {
      const params = {
        Bucket: process.env.IMAGE_BUCKET_NAME,
        Fields: { Key: fileName }
      };
      return new Promise((resolve, reject) =>
        s3Client.createPresignedPost(params, (error, result) =>
          error ? reject(error) : resolve(result)
        )
      );
    };
    
    だから、この機能で何が起こるか?
    まず、抽出するuserName からevent オブジェクト.APIゲートウェイと認知制御は、私たちの関数へのアクセスを制御するので、ユーザはevent 関数が呼び出されたときにオブジェクトを返します.
    次に、ユニークな作成fileName 乱数、現在のタイムスタンプ、およびユーザー名に基づきます.
    The createPresignedUploadCredentials ヘルパー機能は、事前に署名されたS 3 URLを作成します.を返します.url and fields 属性.
    私たちのAPIクライアントはurl そしてすべてのフィールドとファイルを本体に含めます.

    画像認識の統合
    さて、サードパーティの画像認識サービスを統合する必要があります.
    イメージがアップロードされると、S 3はラムダ関数で処理されるアップロードイベントを発生します.
    これは、最初の記事の最初からの質問の一つに私たちをもたらします.

    ラムダからラムダを呼ぶ方法
    簡単に答えなさい.
    なぜ?AWS - SDKを通してラムダから直接ラムダを呼ぶことができる間、それは問題をもたらします.私たちは、何かが失敗するならば、起こる必要があるすべてのものを実行しなければなりません.また、いくつかのケースでは、呼び出し元のラムダは、呼び出されたラムダが終了するのを待たなければなりません、そして、我々も待ち時間の代金を払わなければなりません.
    それで、選択肢は何ですか?
    ラムダはイベントベースのシステムです.行動の主要なコースはそうする別のサービスを使用することです.
    私たちの場合、ファイルアップロードが終わったときラムダを呼び出したいので、S 3イベントをソースとして使用しなければなりません.しかし、他のイベントソースもあります.

  • Step Functions 状態機械によるラムダ関数の調整

  • SQS は、他のランダーで拾うことができるように我々の結果をプッシュすることができますキューです

  • SNS つのラムダ結果を並列に他の多くのランダスに送達することができるサービスです.
  • S 3で呼び出されるSAMテンプレートに新しいラムダ関数を追加します.
    AWSTemplateFormatVersion: "2010-09-09"
    Transform: "AWS::Serverless-2016-10-31"
    Description: "An example REST API build with serverless technology"
    
    Globals:
    
      Function:
        Runtime: nodejs8.10
        Handler: index.handler
        Timeout: 30 
        Tags:
          Application: Serverless API
    
    Resources:
    
      ServerlessApi:
        Type: AWS::Serverless::Api
        Properties:
          StageName: Prod
          Cors: "'*'"
          Auth:
            DefaultAuthorizer: CognitoAuthorizer
            Authorizers:
              CognitoAuthorizer:
                UserPoolArn: !GetAtt UserPool.Arn
          GatewayResponses:
            UNAUTHORIZED:
              StatusCode: 401
              ResponseParameters:
                Headers:
                  Access-Control-Expose-Headers: "'WWW-Authenticate'"
                  Access-Control-Allow-Origin: "'*'"
                  WWW-Authenticate: >-
                    'Bearer realm="admin"'
    
      # ============================== Auth ==============================
      AuthFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/auth/
          Environment:
            Variables:
              USER_POOL_ID: !Ref UserPool
              USER_POOL_CLIENT_ID: !Ref UserPoolClient
          Events:
            Signup:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /signup
                Method: POST
                Auth:
                  Authorizer: NONE
            Signin:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /signin
                Method: POST
                Auth:
                  Authorizer: NONE
    
      PreSignupFunction:
        Type: AWS::Serverless::Function
        Properties:
          InlineCode: |
            exports.handler = async event => {
              event.response = { autoConfirmUser: true };
              return event;
            };
    
      UserPool:
        Type: AWS::Cognito::UserPool
        Properties:
          UserPoolName: ApiUserPool
          LambdaConfig:
            PreSignUp: !GetAtt PreSignupFunction.Arn
          Policies:
            PasswordPolicy:
              MinimumLength: 6
    
      UserPoolClient:
        Type: AWS::Cognito::UserPoolClient
        Properties:
          UserPoolId: !Ref UserPool
          ClientName: ApiUserPoolClient
          GenerateSecret: no
    
      LambdaCognitoUserPoolExecutionPermission:
        Type: AWS::Lambda::Permission
        Properties: 
          Action: lambda:InvokeFunction
          FunctionName: !GetAtt PreSignupFunction.Arn
          Principal: cognito-idp.amazonaws.com
          SourceArn: !Sub 'arn:${AWS::Partition}:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${UserPool}'
    
      # ============================== Images ==============================
      ImageBucket:
        Type: AWS::S3::Bucket
        Properties: 
          AccessControl: PublicRead
          CorsConfiguration:
            CorsRules:
              - AllowedHeaders:
                  - "*"
                AllowedMethods:
                  - HEAD
                  - GET
                  - PUT
                  - POST
                AllowedOrigins:
                  - "*"
    
      ImageBucketPublicReadPolicy:
        Type: AWS::S3::BucketPolicy
        Properties:
          Bucket: !Ref ImageBucket
          PolicyDocument:
            Statement:
              - Action: s3:GetObject
                Effect: Allow
                Principal: "*"
                Resource: !Join ["", ["arn:aws:s3:::", !Ref "ImageBucket", "/*" ]]
    
      ImageFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/images/
          Policies:
            - AmazonS3FullAccess
          Environment:
            Variables:
              IMAGE_BUCKET_NAME: !Ref ImageBucket
          Events:
            CreateImage:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /images
                Method: POST
    
      TagsFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/tags/
          Environment:
            Variables:
              PARAMETER_STORE_CLARIFAI_API_KEY: /serverless-api/CLARIFAI_API_KEY_ENC
          Policies:
            - AmazonS3ReadOnlyAccess # Managed policy
            - Statement: # Inline policy document
              - Action: [ 'ssm:GetParameter' ]
                Effect: Allow
                Resource: '*'
          Events:
            ExtractTags:
              Type: S3
              Properties:
                Bucket: !Ref ImageBucket
                Events: s3:ObjectCreated:*
    
    Outputs:
    
      ApiUrl:
        Description: The target URL of the created API
        Value: !Sub "https://${ServerlessApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
        Export:
          Name: ApiUrl
    
    新しいS 3オブジェクトを作成したときに呼び出されるS 3イベントを持つ新しいServerless関数リソースを追加しますImageBucket .
    我々のラムダ関数でサードパーティAPI、すなわちClarifai APIを呼び出す必要が次の質問に私たちをもたらします.

    サードパーティのAPIのためのストア資格情報?
    そうする方法がたくさんあります.
    つの方法は、KMSと資格情報を暗号化することです.我々は、CLIを介して暗号化キーをテンプレートに追加します.環境変数としてのYAML、そしてラムダの内部では、AWS - SDKを使用して、それらを使用する前に資格情報を解読します.
    別の方法は、AWSシステムマネージャのパラメータストアを使用することです.このサービスは、ラムダの中でAWS - SDKを通して検索されることができる暗号化されたストリングを保存するのを許します.私たちは、資格証明書がどこに格納されるかを定義する名前で我々のラムダを提供するだけです.
    この例では、パラメータストアを使用します.
    あなたが作成しないならばClarifai アカウント、今は時間です.彼らはAPIキーを使用して、次のパラメータストアに格納する必要があります.
    aws ssm put-parameter \
    --name "/serverless-api/CLARIFAI_API_KEY" \
    --type "SecureString" \ 
    --value "<CLARIFAI_API_KEY>"
    
    このコマンドはキーをパラメータストアに入れて暗号化します.
    次に、環境変数を介してラムダ関数を名前について伝える必要がありますgetParameter AWS - SDK経由で.
    Environment:
            Variables:
              PARAMETER_STORE_CLARIFAI_API_KEY: /serverless-api/CLARIFAI_API_KEY_ENC
          Policies:
            - Statement:
              - Action: [ 'ssm:GetParameter' ]
                Effect: Allow
                Resource: '*'
    
    JavaScriptの側面を見てみましょうfunctions/tags/index.js .
    const AWS = require("aws-sdk");
    const Clarifai = require("clarifai");
    
    exports.handler =  async event => {
      const record = event.Records[0];
      const bucketName = record.s3.bucket.name;
      const fileName = record.s3.object.key;
      const tags = await predict(`https://${bucketName}.s3.amazonaws.com/${fileName}`);
    
      await storeTagsSomewhere({ fileName, tags });
    };
    
    const ssm = new AWS.SSM();
    const predict = async imageUrl => {
      const result = await ssm.getParameter({
        Name: process.env.PARAMETER_STORE_CLARIFAI_API_KEY, 
        WithDecryption: true
      }).promise();
    
      const clarifaiApp = new Clarifai.App({
        apiKey: result.Parameter.Value
      });
    
      const model = await clarifaiApp.models.initModel({
        version: "aa7f35c01e0642fda5cf400f543e7c40",
        id: Clarifai.GENERAL_MODEL
      });
    
      const clarifaiResult = await model.predict(imageUrl);
    
      const tags = clarifaiResult.outputs[0].data.concepts
        .filter(concept => concept.value > 0.9)
        .map(concept => concept.name);
      return tags;
    };
    
    ハンドラはS 3オブジェクト作成イベントで呼び出されます.つだけのレコードがありますが、我々はまた、バッチレコードにS 3を伝えることができます.
    その後、新しく作成されたイメージのURLを作成し、predict 関数.
    The predict 関数はPARAMETER_STORE_CLARIFAI_API_KEY 環境変数パラメータストア内のパラメータ名を取得します.これにより、ラムダコードへの変更なしにターゲットパラメーターを変更できます.
    APIキーを復号化し、サードパーティAPIへの呼び出しを行うことができます.それから、我々はタグをどこかに格納します.

    リストとタグ付け画像の削除
    我々は画像をアップロードすることができますし、彼らが自動的にタグ付けを取得すると、次のステップは、すべての画像を一覧表示するには、タグによってフィルタリングし、それらを削除する必要はありません.
    SAMテンプレートを更新しましょう!
    AWSTemplateFormatVersion: "2010-09-09"
    Transform: "AWS::Serverless-2016-10-31"
    Description: "An example REST API build with serverless technology"
    
    Globals:
    
      Function:
        Runtime: nodejs8.10
        Handler: index.handler
        Timeout: 30 
        Tags:
          Application: Serverless API
    
    Resources:
    
      ServerlessApi:
        Type: AWS::Serverless::Api
        Properties:
          StageName: Prod
          Cors: "'*'"
          Auth:
            DefaultAuthorizer: CognitoAuthorizer
            Authorizers:
              CognitoAuthorizer:
                UserPoolArn: !GetAtt UserPool.Arn
          GatewayResponses:
            UNAUTHORIZED:
              StatusCode: 401
              ResponseParameters:
                Headers:
                  Access-Control-Expose-Headers: "'WWW-Authenticate'"
                  Access-Control-Allow-Origin: "'*'"
                  WWW-Authenticate: >-
                    'Bearer realm="admin"'
    
      # ============================== Auth ==============================
      AuthFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/auth/
          Environment:
            Variables:
              USER_POOL_ID: !Ref UserPool
              USER_POOL_CLIENT_ID: !Ref UserPoolClient
          Events:
            Signup:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /signup
                Method: POST
                Auth:
                  Authorizer: NONE
            Signin:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /signin
                Method: POST
                Auth:
                  Authorizer: NONE
    
      PreSignupFunction:
        Type: AWS::Serverless::Function
        Properties:
          InlineCode: |
            exports.handler = async event => {
              event.response = { autoConfirmUser: true };
              return event;
            };
    
      UserPool:
        Type: AWS::Cognito::UserPool
        Properties:
          UserPoolName: ApiUserPool
          LambdaConfig:
            PreSignUp: !GetAtt PreSignupFunction.Arn
          Policies:
            PasswordPolicy:
              MinimumLength: 6
    
      UserPoolClient:
        Type: AWS::Cognito::UserPoolClient
        Properties:
          UserPoolId: !Ref UserPool
          ClientName: ApiUserPoolClient
          GenerateSecret: no
    
      LambdaCognitoUserPoolExecutionPermission:
        Type: AWS::Lambda::Permission
        Properties: 
          Action: lambda:InvokeFunction
          FunctionName: !GetAtt PreSignupFunction.Arn
          Principal: cognito-idp.amazonaws.com
          SourceArn: !Sub 'arn:${AWS::Partition}:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${UserPool}'
    
      # ============================== Images ==============================
      ImageBucket:
        Type: AWS::S3::Bucket
        Properties: 
          AccessControl: PublicRead
          CorsConfiguration:
            CorsRules:
              - AllowedHeaders:
                  - "*"
                AllowedMethods:
                  - HEAD
                  - GET
                  - PUT
                  - POST
                AllowedOrigins:
                  - "*"
    
      ImageBucketPublicReadPolicy:
        Type: AWS::S3::BucketPolicy
        Properties:
          Bucket: !Ref ImageBucket
          PolicyDocument:
            Statement:
              - Action: s3:GetObject
                Effect: Allow
                Principal: "*"
                Resource: !Join ["", ["arn:aws:s3:::", !Ref "ImageBucket", "/*" ]]
    
      ImageFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/images/
          Policies:
            - AmazonS3FullAccess
          Environment:
            Variables:
              IMAGE_BUCKET_NAME: !Ref ImageBucket
          Events:
            ListImages:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /images
                Method: GET
            DeleteImage:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /images/{imageId}
                Method: DELETE
            CreateImage:
              Type: Api
              Properties:
                RestApiId: !Ref ServerlessApi
                Path: /images
                Method: POST
    
      TagsFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: functions/tags/
          Environment:
            Variables:
              PARAMETER_STORE_CLARIFAI_API_KEY: /serverless-api/CLARIFAI_API_KEY_ENC
          Policies:
            - AmazonS3ReadOnlyAccess # Managed policy
            - Statement: # Inline policy document
              - Action: [ 'ssm:GetParameter' ]
                Effect: Allow
                Resource: '*'
          Events:
            ExtractTags:
              Type: S3
              Properties:
                Bucket: !Ref ImageBucket
                Events: s3:ObjectCreated:*
    
    Outputs:
    
      ApiUrl:
        Description: The target URL of the created API
        Value: !Sub "https://${ServerlessApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
        Export:
          Name: ApiUrl
    
    いくつかの新しいAPIイベントを追加しますImagesFunction そして今もJavaScriptを更新する必要があります.
    const AWS = require("aws-sdk");
    
    exports.handler = async event => {
      switch (event.httpMethod.toLowerCase()) {
        case "post":
          return createImage(event);
        case "delete":
          return deleteImage(event);
        default: 
          return listImages(event);
      }
    };
    
    const createImage = async event => {
      const userName = extractUserName(event);
      const fileName = "" + Math.random() + Date.now() + "+" + userName;
      const { url, fields } = await createPresignedUploadCredentials(fileName);
      return response({
        formConfig: {
          uploadUrl: url,
          formFields: fields
        }
      }, 201);
    };
    
    const deleteImage = async event => {
      const { imageId } = event.pathParameters;
      await deleteImageSomewhere(imageId);
      return response({ message: "Deleted image: " + imageId });
    };
    
    // Called with API-GW event
    const listImages = async event => {
      const { tags } = event.queryStringParameters;
      const userName = extractUserName(event);
      const images = await loadImagesFromSomewhere(tags.split(","), userName);
      return response({ images });
    };
    
    // ============================== HELPERS ==============================
    
    const extractUserName = event => event.requestContext.authorizer.claims["cognito:username"];
    
    const response = (data, statusCode = 200) => ({
      statusCode,
      body: JSON.stringify(data),
      headers: { "Access-Control-Allow-Origin": "*" }
    });
    
    const s3Client = new AWS.S3();
    const createPresignedUploadCredentials = fileName => {
      const params = {
        Bucket: process.env.IMAGE_BUCKET_NAME,
        Fields: { Key: fileName }
      };
    
      return new Promise((resolve, reject) =>
        s3Client.createPresignedPost(params, (error, result) =>
          error ? reject(error) : resolve(result)
        )
      );
    };
    
    削除機能はまっすぐですが、また、我々の質問のいずれかを答えます.

    クエリ文字列またはルートパラメータを使用するには?
    const deleteImage = async event => {
      const { imageId } = event.pathParameters;
      ...
    };
    
    APIゲートウェイイベントオブジェクトはpathParameters イベントのSAMテンプレートで定義したパラメータの全てを保持する属性.我々の場合imageId 我々が定義したのでPath: /images/{imageId} .
    同様の方法で使用するクエリ文字列.
    const listImages = async event => {
      const { tags } = event.queryStringParameters;
      ...
    };
    
    コードの残りはあまり複雑ではない.画像を読み込むか削除するには、偶数データを使用します.
    クエリ文字列は、queryStringParameters 属性はevent オブジェクト.

    結論
    時々、それは新しいテクノロジーがパラダイムシフトをもたらすことを起こります.Serverlessはこれらの技術の一つです.
    それの完全な力はしばしば可能な限り管理サービスを使用するためにしばしば沸騰します.独自の暗号化、認証、ストレージ、または計算をロールバックしないでください.クラウドプロバイダが既に実装しているものを使用します.
    ここで大きな問題はしばしば物事を行う方法がたくさんあることです.正しい方法は一つだけありません.我々は直接KMSを使用する必要がありますか?システムマネージャは私たちのためにものを扱うべきですか?ラムダを介してAuthを実現すべきか?我々は認知を使用してそれを行う必要がありますか?
    ラムダをある程度使ったときに起こる質問に答えることができればいいのに.
    もともとはMoesif Blog