3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS ChaliceとAWS CDKを統合して、L2 ConstructでStepFunctionsを定義してみた

Posted at

はじめに

みずほリサーチ&テクノロジーズ株式会社 先端技術研究部の@yosuke_kosakaです。
今回は、AWS ChaliceとAWS CDKの統合の発展形として、AWS CDKのStep FunctionsのL2 Constructを利用して、Chaliceで定義したAPIとStep Functionsを統合しました。

そもそもAWS Chaliceとは?

サーバーレスアプリケーションをAWSで作成するためのPythonのフレームワークです。
少量のコードで、API GatewayとLambdaを数分で作成してくれます。

【参考】
Chalice公式
爆速 API 開発を実現するサーバーレスアプリケーション開発向けフレームワーク

前提

ChaliceのQuick Startの序盤の設定が完了しているものとします。

AWS ChaliceとAWS CDKの統合

Chalice単独で利用する場合、chalice deployのコマンドでAPIのデプロイが可能ですが、
CDKを利用することで、DynamoDB等、他の資産も含めてデプロイすることが可能となります。

【参考】
Deploying with the AWS CDK — AWS Chalice

CDKを利用したChaliceプロジェクトの作成

Chaliceを、AWS CDKを利用してデプロイします。

Chaliceプロジェクトの新規作成時、[CDK] Rest API with a DynamoDB tableを選ぶことでCDKと統合されたプロジェクトが作成されます。

$ chalice new-project


   ___  _  _    _    _     ___  ___  ___
  / __|| || |  /_\  | |   |_ _|/ __|| __|
 | (__ | __ | / _ \ | |__  | || (__ | _|
  \___||_||_|/_/ \_\|____||___|\___||___|


The python serverless microframework for AWS allows
you to quickly create and deploy applications using
Amazon API Gateway and AWS Lambda.

Please enter the project name
[?] Enter the project name: cdk-sfntest
[?] Select your project type: [CDK] Rest API with a DynamoDB table
   REST API
   S3 Event Handler
   Lambda Functions only
   Legacy REST API Template
 > [CDK] Rest API with a DynamoDB table

Your project has been generated in ./cdk-sfntest

フォルダ構成

$ tree
.
├── infrastructure
│   ├── app.py
│   ├── cdk.json
│   ├── requirements.txt
│   └── stacks
│       ├── chaliceapp.py
│       └── __init__.py
├── README.rst
├── requirements.txt
└── runtime
    ├── app.py
    └── requirements.txt

3 directories, 9 files

runtime/app.py(Chaliceのコード)には以下のようなコードが初期設定されています。

  • create_userメソッド
  • get_userメソッド
runtime/app.py
import os
import boto3
from chalice import Chalice


app = Chalice(app_name='cdk-sfntest')
dynamodb = boto3.resource('dynamodb')
dynamodb_table = dynamodb.Table(os.environ.get('APP_TABLE_NAME', ''))


@app.route('/users', methods=['POST'])
def create_user():
    request = app.current_request.json_body
    item = {
        'PK': 'User#%s' % request['username'],
        'SK': 'Profile#%s' % request['username'],
    }
    item.update(request)
    dynamodb_table.put_item(Item=item)
    return {}


@app.route('/users/{username}', methods=['GET'])
def get_user(username):
    key = {
        'PK': 'User#%s' % username,
        'SK': 'Profile#%s' % username,
    }
    item = dynamodb_table.get_item(Key=key)['Item']
    del item['PK']
    del item['SK']
    return item

infrastructure/stacks/chaliceapp.pyには以下のコードが初期設定されています。
__init__内でChaliceが定義されており、runtimeフォルダがソースディレクトリとして利用されています。
DynamoDBもこちらで定義されています。

import os

from aws_cdk import aws_dynamodb as dynamodb

try:
    from aws_cdk import core as cdk
except ImportError:
    import aws_cdk as cdk

from chalice.cdk import Chalice

RUNTIME_SOURCE_DIR = os.path.join(
    os.path.dirname(os.path.dirname(__file__)), os.pardir, 'runtime')

class ChaliceApp(cdk.Stack):

    def __init__(self, scope, id, **kwargs):
        super().__init__(scope, id, **kwargs)
        self.dynamodb_table = self._create_ddb_table()
        self.chalice = Chalice(
            self, 'ChaliceApp', source_dir=RUNTIME_SOURCE_DIR,
            stage_config={
                'environment_variables': {
                    'APP_TABLE_NAME': self.dynamodb_table.table_name
                }
            }
        )
        self.dynamodb_table.grant_read_write_data(
            self.chalice.get_role('DefaultRole')
        )

    def _create_ddb_table(self):
        dynamodb_table = dynamodb.Table(
            self, 'AppTable',
            partition_key=dynamodb.Attribute(
                name='PK', type=dynamodb.AttributeType.STRING),
            sort_key=dynamodb.Attribute(
                name='SK', type=dynamodb.AttributeType.STRING
            ),
            removal_policy=cdk.RemovalPolicy.DESTROY)
        cdk.CfnOutput(self, 'AppTableName',
                      value=dynamodb_table.table_name)
        return dynamodb_table

infrastructure/app.py(CDKのapp.py)には以下のコードが初期設定されています。
CDKでChaliceを利用する場合、stacks.chaliceappからChaliceAppのスタックをインポートして利用することで統合が可能です。

ruinfrastructurentime/app.py
#!/usr/bin/env python3
try:
    from aws_cdk import core as cdk
except ImportError:
    import aws_cdk as cdk
from stacks.chaliceapp import ChaliceApp

app = cdk.App()
ChaliceApp(app, 'cdk-sfntest')

app.synth()

CDKを利用してデプロイ

必要なライブラリをインストールします。

pip3 install aws-cdk-lib
pip3 install boto3

初期状態のままデプロイしてみます。infrastructureフォルダでcdk deployを実行します。

cd cdk-sfntest/infrastructure
cdk deploy

Do you wish to deploy these changes (y/n)? y
cdk-sfntest: deploying... [1/1]

・・・

✨  Total time: 114.9s

完了すると以下のリソースがCDKによって作成されます。

  • API Gateway
  • Lambda
  • DynamoDB

ChaliceのAPIを、CDKのStep Funtionsで利用する

infrastructure/stacks/sfn_stack.pyを作成し、以下のコードを入力します。

infrastructure/stacks/sfn_stack.py
from aws_cdk import (
    Stack,
    aws_stepfunctions_tasks as tasks,
    aws_stepfunctions as sfn,
    aws_apigateway,
)


class StepfunctionsStack(Stack):
    def __init__(self, scope, id: str, api_stack: any, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # スタック内のAPIを取得
        user_api = aws_apigateway.RestApi.from_rest_api_id(
            self, "user", api_stack.chalice.get_resource("RestAPI").ref
        )

        # ユーザー作成
        create_user_task = tasks.CallApiGatewayRestApiEndpoint(
            self,
            id="ユーザー作成",
            api=user_api,
            stage_name="api",
            method=tasks.HttpMethod.POST,
            api_path="/users",
            auth_type=tasks.AuthType.IAM_ROLE,
            request_body=sfn.TaskInput.from_json_path_at("$"),
            input_path="$",
            result_path="$.CreateResult",
            headers=sfn.TaskInput.from_object({
              "Content-Type": [
                "application/json"
              ]
            })
        )

        # ユーザー取得
        get_user_task = tasks.CallApiGatewayRestApiEndpoint(
            self,
            id="ユーザー取得",
            api=user_api,
            stage_name="api",
            method=tasks.HttpMethod.GET,
            api_path=sfn.JsonPath.format(
                "/users/{}", sfn.JsonPath.string_at("$.username")
            ),
            auth_type=tasks.AuthType.IAM_ROLE,
            request_body=sfn.TaskInput.from_json_path_at("$"),
            input_path="$",
            headers=sfn.TaskInput.from_object({
              "Content-Type": [
                "application/json"
              ]
            }
            )
        )

        # definition作成
        definition = sfn.Chain.start(create_user_task).next(
            get_user_task
        )

        # StateMachine定義
        sfn.StateMachine(
            self,
            "UserCreateStateMachine",
            definition=definition,
            state_machine_type=sfn.StateMachineType.STANDARD,
        )

AWS CDKのStep FunctionsとChalice APIとの統合のポイントは以下です。

get_resource

Chalice #get_resource
によると、get_resource(resource_name)によってChaliceで作成されたリソースをCDKのリソースとして取り込み可能です。

しかし、単純にget_resourceでAPIを取得すると、aws_cdk.cdk.CfnResource型で返されてしまい、L1のコンストラクトになってしまいます。
このままでは、StepFunctionsのL2コンストラクトが使えず、Step Functionsのステートマシン定義はJSONで記入する必要が出てしまいます。

from_rest_api_idの利用

aws_apigateway.RestApiのfrom_rest_api_idメソッドを使うことで、APIのIDから取り込むことが可能です。
これを利用して、以下のように定義しました。
※RESTAPIというリソース名で定義されています。

        # スタック内のAPIを取得
        user_api = aws_apigateway.RestApi.from_rest_api_id(
            self, "user", api_stack.chalice.get_resource("RestAPI").ref
        )

taskで利用

tasksの、CallApiGatewayRestApiEndpoint内のapiに、上記from_rest_api_idで取得したuser_apiを指定します。
こうすることでAWS CDKのStep FunctionsとChalice APIとの統合が完了します。

        # ユーザー作成
        create_user_task = tasks.CallApiGatewayRestApiEndpoint(
            self,
            id="ユーザー作成",
            api=user_api,
            stage_name="api",
            method=tasks.HttpMethod.POST,
            api_path="/users",
            auth_type=tasks.AuthType.IAM_ROLE,
            request_body=sfn.TaskInput.from_json_path_at("$"),
            input_path="$",
            result_path="$.CreateResult",
            headers=sfn.TaskInput.from_object({
              "Content-Type": [
                "application/json"
              ]
            })
        )

StateMachineを定義

必要なtaskを定義できたら、後はdefinitionとStateMachineを定義して完成です。
JSONで定義するよりも簡単にStateMachineを利用できるので、CDKの恩恵を受けられます。

        # definition作成
        definition = sfn.Chain.start(create_user_task).next(
            get_user_task
        )

        # StateMachine定義
        sfn.StateMachine(
            self,
            "UserCreateStateMachine",
            definition=definition,
            state_machine_type=sfn.StateMachineType.STANDARD,
        )

infrastructure/app.pyの修正

Chaliceスタックを変数に格納し、作成したStepFunctionsのスタックを追加します。

#!/usr/bin/env python3
try:
    from aws_cdk import core as cdk
except ImportError:
    import aws_cdk as cdk
from stacks.chaliceapp import ChaliceApp
from stacks.sfn_stack import StepfunctionsStack


app = cdk.App()
# Chaliceのスタックを変数に格納
api_stack = ChaliceApp(app, 'cdk-sfntest')

# ChaliceスタックをStepFunctionsスタックに渡す
StepfunctionsStack(app,'sfn-stack',api_stack)

app.synth()

デプロイ前

pip install -r requirements.txt
cd ./infrastructure
pip install -r requirements.txt

デプロイ

infrastructureフォルダでcdk deployを実行します。

cd infrastructure
cdk deploy --all

✨  Deployment time: 93.08s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/sfn-stack/XXXXXXXXXXXXXXXXXXXXXXXXXX

✨  Total time: 106.1s

確認

ステートマシンが作成されていることが確認できます。

statemachine.png

※今回はL2コンストラクトを利用してステートマシンを定義することに主観を置いており、作ったステートマシンは単純にAPIを繋いだだけのものですので、動作に関しては検証しておりません。

まとめ

今回はChalice+CDKを利用してステートマシンを作る方法をご紹介しました。
CDKのL2以上のコンストラクタは非常に便利に作られており、利用することで開発効率も上げることが可能ですので、
Step Functionsに限らず、他のサービスとも統合してより効率的に開発していきましょう。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?