AWS SAMのテンプレートではリソースごとに!Refと!GetAttの戻り値が違う

# はじめに
この記事では、AWS SAMテンプレートでAWS Lambda、AmazonAPI Gateway、AmazonSNSを使用したサーバレスアプリケーションを作ろうとしたときに躓いたポイントを紹介します。
# AWS SAM テンプレートとは
AWS SAMのテンプレートはAWS CloudFormation テンプレートの拡張で基本的にはCloudFormationのテンプレートの書き方と同じですがSAM特有の便利なコンポーネントも追加されています。
もっと詳しく知りたい方はこちらを参考にしてみてください。
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-reference.html
#CloudFormationの組み込み関数
CloudFormationにはスタックの管理に便利な組み込み関数があります。ここではそれらの関数のうち2つを紹介します。便利な使い方としては、実行するまでわからない値（SAMテンプレートで作成したLambda関数のAmazonリソースネーム (ARN)だったり、SNSのTopicNameだったり）を取得してプロパティに代入することができます。
## Fn::GetAtt（!GetAtt）
まずFn::GetAtt（!GetAtt）について紹介します。短縮形の構文で!GetAttとかかれることが大きので以下!GetAttとして説明します。!GetAtt組み込み関数は戻り値としてテンプレートのリソースから属性の値（リソース名やARNなど）を返します。例えばテンプレートで以下のようにLambdaを定義したとしましょう。

```yaml:template.yaml
Resources:
  SampleLambda:
    Type:AWS::Serverless::Function
    Properties:
      FunctionName: SampleFunction
      Runtime: python3.7
      CodeUri: test/
      Handler: app.lambda_handler
```
テンプレートにLambdaを定義しただけではLambda関数は作成されません。当然、buildとdeployをしなければなりません。buildとdeployを行うとLambda関数が作成されARNなどの値も生成されます。ということは、もしテンプレートを書いてリソースなどを定義している段階でLambdaのARNが必要になったとしてもdeployしてから手動でARNをコピーしてテンプレートに付け足してまたdeployしなければいけないの？？とその時は悲しい気持ちでテンプレートを書いてました。そこで発見したのがこの!GetAttという組み込み関数です。以下の例を参考に使い方をみていきます。

```yaml:template.yaml
Resources:
  SampleLambda:
    Type:AWS::Serverless::Function
    Properties:
      FunctionName: SampleFunction
      Runtime: python3.7
      CodeUri: test/
      Handler: app.lambda_handler
      Role: !GetAtt SampleIamRole.Arn

  SampleIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
```
SampleLambdaに対するSampleIamRoleというRoleを定義しました。このRoleをLambdaに紐付けるときに使用するのが!GettAtt関数です。

```yaml:template.yaml
Role: !GettAtt SampleIamRole.Arn
```
これを付け足すと作成したRoleのARNを取得しLambdaに紐づけてくれます。すごく便利です。
## Fn::Ref（!Ref）
次にFn::Ref（!Ref）について紹介します。短縮形の構文で!Refとかかれることが大きので以下!Refとして説明します。!Ref組み込み関数は戻り値として指定したパラメータまたはリソースの値を返します。先ほどと同じようにテンプレートで以下のようにLambdaを定義します。

```yaml:template.yaml
Resources:
  SampleLambda:
    Type:AWS::Serverless::Function
    Properties:
      FunctionName: SampleFunction
      Runtime: python3.7
      CodeUri: test/
      Handler: app.lambda_handler
      Events:
        SNS:
          Type: SNS
          Properties:
            Topic: !Ref SampleTopic

  SampleTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: SampleTopic
```
Lambdaのトリガー（イベント）としてSNSのトピックを定義しました。イベントとしてSNSのトピックを設定するときはトピックのARNが必要です。そこで先ほどの!GetAtt関数を使用してARNを取得しようとしてもうまくいきません。ここが今回紹介する躓きポイントです。ドキュメントをみてみるとこんなことが書かれています。
>戻り値
参照番号
トピック ARN (例: arn:aws:sns:us-east-1:123456789012:mystack-mytopic-NZJ5JSMVGFIE) このリソースの論理 ID を組み込みの Ref 関数に渡すと、Ref は次を返します: 。
For more information about using the Ref function, see Ref.
Fn::GetAtt
Fn::GetAtt 組み込み関数は、このタイプの指定された属性の値を返します。以下には、利用可能な属性とサンプル戻り値のリストが示されます。
Fn::GetAtt 組み込み関数の使用方法の詳細については、「Fn::GetAtt」を参照してください。
TopicName
Amazon SNS トピックの名前を返します。

AWS::SNS::Topicタイプリソースに対して!GetAtt関数を使用すると戻り値として返ってくるのはARNではなくSNS トピックの名前です。!Refを使用すると戻り値としてSNSトピックののARNが返ってきます。
ARNを取得しようとして!GetAttや!Refを使用してもリソースによっては!Refで取得できたり、!GetAttで取得できる違いがあるようです。
# まとめ
- 取得したい値によって!GetAttや!Refを使い分けるだけではなく、同じような値(ARNやリソース名など)を取得するにしてもリソースによって使い分けなければいけない
- とにかくドキュメントに全て書いてあるから読み込む

というのがすごく大事だなと痛感しました。