【AWS CDK】既存のASLを有効活用したStep FunctionsのCDK実装 ~ASL読み込み/実装編~

## はじめに
Step Functionsを実装する時どうされていますでしょうか？
私は、検討しながら、試しながらマネージメントコンソールからポチポチと作ってしまうのが大半です。
このポチポチで作るとASL(Amazon States Language)で出力することができるので、正式な環境を作る時うまい具合に流用したいのですが、出力されたASLだと連携するリソースのARN等がハードコーディングされており、リファクタリングしないと流用できないです。

また、正式な環境を作る時は、IaCを活用したいので、CDKでStep Functionsを実装する場合、連携するリソースも併せて実装する都合上普通なら作成したASLを元にCDK L2 Constructsで検討し直すアプローチだと思います。
[CDK Constructsに関して](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/constructs.html)

個人的に、ポチポチで作ったベースがあるのにがっつり流用できないのはもったいないなと思ってしまいました。
ここから、ポチポチで作ったASLを元にASLのベーステンプレート手動で作成し、そのベーステンプレートからパラメータを置換する処理をCDKに組み込めば、作ったASLを有効活用できると思いつき、作成してみましたので、ご紹介させて頂きます。

本内容はフォーカスするポイントが2つあるので、2つの記事に分割させて頂きます。
- ASLパラメータ置換編
既存ASLからベーステンプレートを作成し、そのベーステンプレートからパラメータを置換するTypeScriptの処理のサンプルコード
- ASL読み込み/実装編
「ASLパラメータ置換編」で作成した処理をCDKに組み込み、CDK Deployに合わせて置換させる力技CDK

本記事では、ASL読み込み/実装に関してご紹介させて頂きます。
関連するASLパラメータ置換編に関しては以下のリンク記事をご参照ください。
[【AWS CDK】既存のASLを有効活用したStep FunctionsのCDK実装 ~ASLパラメータ置換編~](https://qiita.com/akwayne/items/3d46c125081a44a0a214)

※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。

## 検討の経緯
APPの構成をしていく中で、以下の変遷をたどりました。
|項番|APP構成|採用可否|
|---|---|---|
|1|1APP 1Stack構成|×|
|2|1APP 2Stack構成|×|
|3|2APP構成|〇|

×となった理由をそれぞれのAPP構成に分けてご紹介させて頂きます。

### 1APP 1Stack構成
以下のようなイメージの構成となります。

#### stackコード
~~~typescript:asl-replace-sample-stack.ts
export class ASLReplaceSampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: ASLReplaceSampleStackProps) {
    super(scope, id, props);

   // State Machineで利用するResourceを作るConstructs
   // Lambda, SNS ObjectがPublicされている
    const preparationResources = new preparationResources (this, "preparationResources");

    // StepFunctions用Constructs
    // Lambda,SNSのObjectはInterfaceで指定
    const stateMachine = new StateMachine(this, "StateMachine", {
      myLambda: preparationResources.myLambda,
      mySns: preparationResources.mySns
    });
~~~~

正直これは、試すまでもなくダメだろうなと思っていました。
理由は、CloudFormationを作成する段階では、埋め込むパラメータの元となるリソースが出来ていないので、合わせて作成する置換後のASLファイルには、TOKENで渡される気がしていました。
結果は以下のような形となり、想定通りでした。

#### 置換後のASLファイル
~~~json:asl-replaced.json
{
  "Comment": "kick Fargate Service",
  "StartAt": "Lambda Invoke",
  "States": {
    "Lambda Invoke": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "Payload.$": "$",
        "FunctionName": "${Token[TOKEN.12]}"
~~~

ASLとしては、TOKENでは意味のないパラメータになってしまうため、目的のStep FunctionsはDeployされません。
以上より、この構成は採用できません。

### 1APP 2Stack構成
以下のようなイメージの構成となります。

#### State Machineで利用するリソースを作るStack
~~~typescript:pre-asl-replace-sample-stack.ts
export class PreASLReplaceSampleStack extends cdk.Stack {
    public myLambda: lambda.Function,
    public mySns: sns.Topic
    
    constructor(scope: Construct, id: string, props: PreASLReplaceSampleStackProps) {
    super(scope, id, props);

       // State Machineで利用するリソースを作るConstructs
       // Lambda, SNS ObjectがPublicされている
        const preparationResources = new preparationResources (this, "preparationResources");
        this.myLambda = preparationResources.myLambda
        this.mySns = preparationResources.mySns
~~~~

#### Step Functions用のStack
~~~typescript:asl-replace-sample-stack.ts
export interface ASLReplaceSampleStackProps extends StackProps {
    public myLambda: lambda.Function,
    public mySns: sns.Topic
}

export class ASLReplaceSampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: ASLReplaceSampleStackProps) {
    super(scope, id, props);

    // StepFunctions用Constructs
    // Lambda,SNSのObjectはInterfaceで指定
    const stateMachine = new Nw(this, "StateMachine", {
      myLambda: props.myLambda,
      mySns: props.mySns
    });
~~~~

#### binのコード
~~~typescript:asl-replace-sample.ts
const  PreASLReplaceSampleStack =new  PreASLReplaceSampleStack(app, ' PreASLReplaceSampleStack', {
});

const ASLReplaceSampleStack = new ASLReplaceSampleStack(app, 'ASLReplaceSampleStack', {
   myLambda: PreASLReplaceSampleStack.myLambda
   mySns: PreASLReplaceSampleStack.mySns
 });

~~~~

正直これも、試すまでもなくダメだろうなと思っていました。
理由は、Deploy時にStackを指定することは出来ますが、CloudFormationを作成する段階ではstackを指定することが出来ません。つまり、2つのStackまとめてCloudFormation化します。なので、「1APP 1Stack構成」と同じで埋め込むパラメータの元となるリソースが出来ていないので、合わせて作成する置換後のASLファイルには、TOKENで渡される気がしていました。
結果は以下のような形となり、想定通りでした。

~~~json:asl-replaced.json
{
  "Comment": "kick Fargate Service",
  "StartAt": "Lambda Invoke",
  "States": {
    "Lambda Invoke": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "Payload.$": "$",
        "FunctionName": "${Token[TOKEN.12]}"
~~~

結果は「1APP 1Stack構成」と同じなので、この構成も採用できません。

以上より、明確にAPPを分けてASLに埋め込むリソースが必ず事前に作られている構成を取る必要があるため、力技ですが、「2APP構成」を取ることとしました。

## 2APP構成の概要
本CDKコードは以下4セクションで構成されています。
- State Machineで利用するリソースを作るAPP

【Step Functions用のAPP】
- 他APPでDeployされたリソースをfrom() Method系でobject化
- ASLファイルを作成する
- ASLファイルからState Machineを作成する


それぞれのセクションで分けてご紹介させて頂きます。

### State Machineで利用するリソースを作るAPP
ここでは、特に特別なことはしません。Public等もしなくて大丈夫です。
普通にAPPを構成し、必要なリソースをDeployしてください。
ポイントは事前にDeployしておくということです。

### 他APPでDeployされたリソースをfrom() Methodでobject化
ここがこの構成のポイントとなります。
作成したリソース情報を引き渡していないので、Deploy環境から取り込んであげる必要があります。

そのためにfrom() Methodを活用します。
また、少しでも再利用性を高めるためにfrom() Methodで利用するパラメータは外だしし、取り込まさせています。
from() Methodを活用することにより、Objectとして取り込めるので、grant Method()等色々活用が可能です。
[grant() Methodに関して](https://qiita.com/akwayne/items/34fe14e008c77f1cf7ac)

~~~typescript:asl-replace-state-machine-sample.ts
export interface ASLReplaceStateMachineSampleProps{
  myLambda: string,
  mySns: string,
}

export class ASLReplaceStateMachineSample extends Construct {
  constructor(scope: Construct, id: string, props: ASLReplaceStateMachineSample) {
    super(scope, id);

     //受け取ったパラメータからObject化
     const myLambdaObj = lambda.Function.fromFunctionName(this, "myLambdaObj", props.myLambda);
     const mySnsObj = sns.Topic.fromTopicArn(this, "mySnsObj", props.const mySnsObj);
~~~~

Lambdaのfrom() MethodはNameから生成できるので、リソースがない状態でもコード自体は作成可能です。
しかし、SNSのfrom() MethodはARNが必要なので、残念ながらリソースのDeployが完了していないとコードの作成すら難しいです。

### ASLファイルを作成する
ここには、以下のリンク記事で紹介させて頂いている置換のためのコード一式を埋め込んで頂ければ問題ありません。
[【AWS CDK】既存のASLを有効活用したStep FunctionsのCDK実装 ~ASLパラメータ置換編~](https://qiita.com/akwayne/items/3d46c125081a44a0a214)

本記事からは割愛させて頂きます。

### ASLファイルからState Machineを作成する

上段で作成したASLファイルを元にState Machineを作成する部分となります。
~~~typescript:asl-replace-state-machine-sample.ts
     const myStateMachine = new sfn.StateMachine(this, "myStateMachine",{
      stateMachineName: `myStateMachine`,
      definitionBody: sfn.DefinitionBody.fromFile(outputaslPath, {}),
      role: myStateMachineRole,
      logs: {
          destination: myStateMachineLogGp,
          level: sfn.LogLevel.ALL
      },
      tracingEnabled: true
     });
~~~~

**definitionBody: sfn.DefinitionBody.fromFile(outputaslPath, {}),**
ここでASLファイルを指定しています。
上段で作成したASLファイルはoutputaslPathに格納されるようになっており、そのパスをそのまま指定しています。


## まとめ
ASL読み込み/実装編は以上になります。
ASLファイルを置換して読み込むというイレギュラーな手法のため、APPを分割する必要があり、力技感がいなめませんが、検証等で作成したASLを有効活用するためのサンプル構成となります。

似たようなことをされたい場合は、ご紹介させて頂いた処理を参考にして頂けると幸いです。

※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません