SNSとLambdaを用いたマイクロサービスのアーキテクチャ

# 目的
SNSとLambdaを用いたマイクロサービスの環境を構築したときのメモです。
今回のアーキテクチャがハマるケースは少ないかもしれませんが、もし誰かの役に立てばと思い残します。

# 経緯
- デバイス(IoT)から呼び出されるクラウドサービスを構築した
- デバイスのバージョンごとに機能の互換性を担保する必要がある
- 互換性のロジックをLambda関数のコードに全部入れるとメンテナンスが大変
- MQTTトピックは既に決定している
- IoTルールは気軽に修正できない

# 構成
- IoT Core
- SNS
- Lambda
## 構成図
![Untitled-3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515577/423e3257-52d0-cd65-115e-7c762a3e9e32.png)

# 特徴
簡単に表現すると Fan-out + Message Broker です。
- 1つのSNSトピックに対して複数のLambda関数がサブスクライブ
- サブスクリプションフィルターでLambda関数が起動する条件を定義
- SNSトピックにメッセージを発行する際にメッセージ属性を設定する

ポイントは **バージョン** を数値として設定することで、特定のバージョン以上/以下といったサブスクリプションフィルターを設定することが出来ます。

**メリット**
- 特定の機能（Lamnbda関数）を新旧バージョンがそれぞれ独立して状態で混在させることができる
- SNSの配信失敗やLambda関数の実行失敗の際にはDLQ経由で簡単に再実行をすることができる
  - 標準的な機能のため今回の記事の範囲には含めていません。
- あるリクエストに対してサブスクリプションフィルターを設定するだけで機能を追加/拡張できる
  - 既存機能に一切の影響を与えずにお試し機能を実装して試してみる
  - ある特定機能が呼び出された時だけSlackに通知する

**デメリット**
- サブスクリプションフィルターの設定を誤ると新旧の機能が両方実行されてしまう
- メッセージ属性の設計をしないと複雑な定義に成長してしまう

# サンプルコード
**環境**
```yaml:sample.cf.yaml
Parameters:
  BrokerTopic:
    Description: Amazon SNS Topic
    Type: String
  SourceCodeBucket:
    Type: String
  SourceCodeObject:
    Type: String
Resources:
  Function:
    Properties:
      Code:
        S3Bucket:
          Ref: SourceCodeBucket
        S3Key:
          Ref: SourceCodeObject
      Handler: main
      Role:
        Fn::GetAtt:
        - Role
        - Arn
      Runtime: go1.x
    Type: AWS::Lambda::Function
  Permission: # サブスクリプションフィルターでLambda関数を起動する場合に必要
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Ref: Function
      Principal: sns.amazonaws.com
      SourceAccount:
        Ref: AWS::AccountId
      SourceArn:
        Ref: BrokerTopic
    Type: AWS::Lambda::Permission
  Trigger: # サブスクリプションフィルターでLambda関数を起動する場合に必要
    Properties:
      Endpoint:
        Ref: Function
      FilterPolicy:
        application:
        - bizlogic
        version:
        - numeric:
          - '>'
          - 0
      Protocol: lambda
      TopicArn:
        Ref: BrokerTopic
    Type: AWS::SNS::Subscription
  Role:
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Action:
          - sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: /
      Policies:
      - PolicyDocument: # SNSトピック経由で次のLambda関数を呼ぶ場合に必要な権限
          Statement:
          - Action:
            - sns:Publish
            Effect: Allow
            Resource:
            - Ref: BrokerTopic
            Sid: SNS
          Version: 2012-10-17
        PolicyName: Broker
    Type: AWS::IAM::Role
```
**SNSトピックにメッセージを発行するコード**
```go:sample.go
req := &sns.PublishInput{
    TopicArn: &topic,
    Message:  &message,
    MessageAttributes: map[string]types.MessageAttributeValue{
        "application": {DataType: aws.String("String"), StringValue: "bizlogic"},
        "version": {DataType: aws.String("Number"), StringValue: "20"}, // バージョンは数値に変換出来る必要がある
    },
}
```

# 所感
IoT Core を管理するチームが別チームのため好きに管理することができなかったため、バージョン互換性の仕組みを考えてみました。
何気にSNSを使う設計を自分でするのは初めてだったので色々と勉強になりました。