AWS CDK で API Gateway を作った後、テストコードに URL を伝えるのは Parameter Store が便利だよという話
AWS CDK では TypeScript を始めとした様々な言語でインフラコードを書くことができます。つい先日、Java と .NET にも対応しましたね。
私は、サーバーレスのアプリケーションを実装する際、構築した API に対しても実際にリクエストを送り、レスポンスを確認するようなテストも書きます。結合テスト、E2Eテストなどと呼ばれているテストですね。そのとき、構築したAPIのURLがほしい という状況がよくあります。AWS SAM でサーバーレスアプリケーションを構築していたときは CloudFormation の Output からURLを取得したりもしました。
AWS CDK で構築した場合、aws-ssmが便利です。キーだけ決めておけば、人の手を極力少なくテストコードでURLを呼び出すことができます。早速試しましょう。
環境
パッケージ名 | バージョン |
---|---|
aws-cdk | 1.19.0 |
jest | 24.9.0 |
ts-jest | 24.2.0 |
ts-node | 8.5.4 |
typescript | 3.7.3 |
やること
- AWS CDK で API Gateway (Mock API)をつくる
- URLを Parameter Store に登録する
- テストコードからURLを取得し、リクエストする
AWS CDK で API Gateway(ダミー)をつくる
サンプルとなる API Gateway を構築します。必要な AWS CDK モジュールをインストールしましょう。
> npm install --save-dev @aws-cdk/aws-apigateway @aws-cdk/aws-ssm
次に、CDK のコードを書きます。
import * as cdk from '@aws-cdk/core'; import * as apig from '@aws-cdk/aws-apigateway'; import * as ssm from '@aws-cdk/aws-ssm'; // ① function での定義も可能 export function cdkStackEvalCdk( scope: cdk.Construct, id: string, ): void { const stack = new cdk.Stack(scope, id); // ダミーAPI定義 const api = new apig.RestApi( stack, 'DummyRestApi', { restApiName: `dummy`, endpointTypes: [apig.EndpointType.REGIONAL], deployOptions: { stageName: 'v1' }, }, ); // GET /user const userResource = api.root.addResource('user'); resourceIntegrationGetUser(userResource, 'GET'); // ② URLを Parameter Store に登録 new ssm.StringParameter(stack, 'UserApiUrl', { parameterName: 'UserApiUrl', stringValue: api.url, }); } function resourceIntegrationGetUser( self: apig.Resource, method: string, ): void { const methodOptions: apig.MethodOptions = { methodResponses: [ { statusCode: '200', }, ], }; const dummyUser = { name: 'wada', description: 'sorry for late' }; const integration = new apig.MockIntegration({ requestTemplates: { 'application/json': `{ "statusCode": 200 }`, }, integrationResponses: [ { statusCode: '200', responseTemplates: { 'application/json': JSON.stringify(dummyUser), }, }, ], }); self.addMethod(method, integration, methodOptions); }
ポイントを見ていきます。
1.AWS CDK の Construct 定義は function でもできる
cdk init
で出来上がる最初のコードが cdk.Construct
をオーバーライドする形になっているので勘違いしがちですが、function でも書けます。そのときは、function内で const stack = new cdk.Stack(scope, id);
のようにして Stacks を、 const stackConstruct = new cdk.Construct(scope, id);
のようにしてConstructを定義できます。
2. aws-ssm
モジュールを使って Parameter Store に登録する
Parameter Store に登録しようとなったとき、私が最初に思いついたのは AWS SDK(CDKではなく)を使って API Gateway のURLを登録することでした。次のようなイメージです:
const params = { Name: 'UserApiUrl', Type: 'String', Value: apig.url }; ssm.putParameter(params).promsise();
しかしこれではうまくいきません。この処理が呼ばれる(= Parameter Store に登録される)時点では、API Gateway はデプロイが完了しておらず、URLが確定していないためです。それじゃあ一体どんな値が入っているのかというと、仮の値が入っています。 console.log
してみると https://${Token[TOKEN.12]}.execute-api.${Token[AWS::Region.4]}.${Token[AWS::URLSuffix.1]}/${Token[TOKEN.18]}/
という値が出力されました。落ちつて考えれば当たり前で、依存関係を解決してデプロイが完了するまでは、API Gateway のURLも確定せず、 Parameter Store に登録することもできませんね。
ではどうするかというと、API Gateway の URLも Parameter Store の "AWSリソース" として構築してしまいます。それをやってくれるのが aws-ssm
です。
new ssm.StringParameter(stack, 'UserApiUrlParameter', { parameterName: 'UserApiUrl', stringValue: api.url, });
このように StringParameter
を登録することで確定した URL をParameter Storeに追加できます。デプロイしましょう。
> cdk deploy
Parameter Store に登録されていることを確認してください。
aws ssm get-parameter --name UserApiUrl { "Parameter": { "Name": "UserApiUrl", "Type": "String", "Value": "https://xxxxyyyzzz.execute-api.ap-northeast-1.amazonaws.com/v1/", "Version": 1, "LastModifiedDate": 1576684620.448, "ARN": "arn:aws:ssm:ap-northeast-1:xxxxxxxxxxx:parameter/UserApiUrl" } }
これで準備完了です。
テストコードからURLを取得し、リクエストする
テストコードのほうは AWS SDK を使います。インストールしておきましょう。
> npm install --save-dev aws-sdk axios
ダミーAPIに対してリクエストし、レスポンスを確認するようなテストを書きます。
import * as aws from 'aws-sdk'; import axios from 'axios'; const ssm = new aws.SSM({ region: 'ap-northeast-1' }); test('Dummy API', async () => { // ① Parameter Store からURLを取得 const param = await ssm.getParameter({ Name: 'UserApiUrl' }).promise(); const url = param.Parameter?.Value!; const userEndpoint = `${url}/user`; // ② URLに対してリクエスト、レスポンスボディを確認する const response = await axios.get(userEndpoint); expect(response.data).toEqual({ name: 'wada', description: 'sorry for late' }); });
デプロイされたダミーAPIのURLを Parameter Store から取得し、リクエストを送ることができました。ここまでURLのコピペは行っていませんし、特別なスクリプトも書いていません。いい感じですね。
まとめ
aws-ssm
を使うことで、 AWS CDK デプロイ時に API Gateway の URL も登録できました。その後、テストコードで実際に Parameter Store から取得してテストを行いました。特にサーバーレスアプリケーションなど、多くのサービスが連携するタイプのシステムでは、実際にデプロイしての動作確認が有効です。そのときに、APIを始めとしたアクセス可能なエンドポイントを Parameter Store に登録しておけば捗ると思います。参考になれば幸いです。