Serverless FrameworkでAmazon API Gatewayのベースパスマッピングを作ろうとした時の話

この記事は [ハンズラボ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/handslab) 22日目の記事です。
昨日は、@jnuankさんの[「来月退職します」から始めるモブプログラミング](https://qiita.com/jnuank/items/ecae6b04279b89a990ef) でした。

はじめまして、今年4月に中途入社し、毎日AWSをがっつり触れて楽しい日々を送る@t-nmrです。
Advent Calendarに参加するの初めてなので何書こうか…？と悩みましたが、
業務でよく触れる機会のあるServerlessで、最近遭遇した内容について書こうと思います。

## 何があったのか
タイトルにもある通り、ServerlessでAPI Gatewayとそのカスタムドメインを作ろうとしたところ、
ベースパスマッピングが上手く作成出来ず、ちょっとハマってしまう事がありました。

何故かというと、ベースパスマッピングはパスと一緒に対象のステージを指定しなければいけないから。
つまり、初回sls deployの時点ではベースパスマッピング作成のタイミングで
まだメソッドがステージに一度もデプロイされておらず、ステージ自体が無い。

API Gatewayと一緒にfunctionsでAPIのメソッドとなるLambdaを作成していたため、
「自動的にServerlessがデプロイやってくれるんだから、そいつをDependsOnで参照してやればいい」
そんな風に考えて何回か試してみたところ、対象リソースの論理IDの名前がコロコロ変わって予測出来ない。[^1]
（論理IDの名前は「ApiGatewayDeploymentXXXXXXXX」といった感じで毎回名前が異なる）

これじゃDependsOnで参照出来ない。凄く困る。

## 色々考えた結果

先にAPI GatewayとLambda作ってから2回に分けてリソース構築するとか、
別途スクリプトあたりで作るとかも考えましたが、やっぱりsls deployコマンド一発で作りたいですよね。

という事で、シンプルにserverless.yml中でステージへのデプロイを別途明示的に行い、
その完了をDependsOnで待つ事にしました。

## ソースコード
こんな感じになりました （一部記載は簡略化しています）

また余談ですが、Deploymentの際にDependsOnで見ているApiGatewayMethodApiV1TestGetも、
Serverlessが自動的に作ってくれるリソースになります。

しかし、このリソースの論理IDは名前の予測が可能 かつ 普通にDependsOnで指定可能です。

(「ApiGatewayMethod + {APIパスのupper camel case} + {HTTPメソッド} 」 という形式で作成されます。
　慣れてる人には常識かと思いますが、初心者だとこういう部分も知らないと地味にハマりかけたりする…)

```YAML
functions:
  test:
    handler: lambda.handler
    timeout: 15
    tracing: true
    events:
      - http:
          path: /api/v1/test
          method: get
          cors: true
          integration: lambda-proxy

resources:
  Resources:
    ApiGatewayRestApi:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Name: test-api
        ApiKeySourceType: HEADER
        EndpointConfiguration:
          Types:
            - REGIONAL

    ApiGatewayDomainName:
      Type: AWS::ApiGateway::DomainName
      Properties: 
        DomainName: ${self:custom.domain_name}
        EndpointConfiguration: 
          Types: 
            - REGIONAL
        RegionalCertificateArn: ${self:custom.acm_arn_regional}

    # BasePathMappingの依存解決（stage作成）のために、デプロイを個別に実行する
    ApiGatewayDeploy:
      Type: AWS::ApiGateway::Deployment
      DependsOn: ApiGatewayMethodApiV1TestGet
      Properties:
        RestApiId:
          Ref: ApiGatewayRestApi
        StageName: ${self:provider.stage}

    ApiGatewayBasePathMapping:
      Type: AWS::ApiGateway::BasePathMapping
      DependsOn: ApiGatewayDeploy # ここで個別に実行したステージへのデプロイ完了を待つ
      Properties:
        BasePath: ""
        DomainName: ${self:custom.domain_name}
        RestApiId:
          Ref: ApiGatewayRestApi
        Stage: ${self:provider.stage}
```

めでたしめでたし。

## とでも思っていたのか？
はい、ここまで書いておいて全てをひっくり返します。

「デプロイの論理IDの名前がコロコロ変わって予測出来ない」と上で書きましたが違いました。
<b>普通に記述出来る方法がありました。</b>

この記事を書いている最中(Advent Calendar投稿当日)に、念のためServerless Framworkの公式ドキュメントを再調査していたら[こんな記載](https://serverless.com/framework/docs/providers/aws/guide/resources/#aws-cloudformation-resource-reference)がありました。

あのランダムだと思っていた数字列はInstanceIdとやらで、${sls:instanceId}って感じで参照出来ますと。

つまり、結論としてはAWS::ApiGateway::Deploymentを書く必要は無く、
「ApiGatewayDeployment${sls:instanceId}」というリソースを
ベースパスマッピング作成時にDependsOnで見てやればオッケー。

……… よし、週明けにserverless.ymlを書き直そう！


## おわり

公式ドキュメントはちゃんと読もう。おじさんとの約束だ。

明日は@o2346さんです。

[^1]: そう思っていた時期が私にもありました。