kakakakakku blog

Weekly Tech Blog: Keep on Learning!

React-Admin を1時間で速習できる!React-Admin Tutorial

React-Admin を使うことになるかもしれず,今まで使ったことがなかったので,まずは「React-Admin Tutorial」を試してみた❗️React-Admin を使ったフロントエンドの実装と基本的な仕組みを速習できて良かった💡おすすめ \( 'ω')/

ちなみに 30 minutes tutorial って書いてあるのは速すぎると思う😅コードを読みながら進めたり,気になった部分をドキュメントで調べながらだと普通に1時間以上かかった〜

marmelab.com

React-Admin Tutorial 実施ログ

React-Admin Tutorial の実施ログを抜粋してまとめておく📝

まず,npm init react-admin test-admin コマンドを実行してプロジェクトをセットアップする.そして npm run dev コマンドを実行すると,すぐに React-Admin の初期画面が表示できる👌

React-Admin 初期画面

React-Admin Tutorial ではバックエンド API として JSONPlaceholder/users API と /posts API を使う.

jsonplaceholder.typicode.com

セットアップ時にデータプロバイダーも作られていて,すぐに API を呼び出せる.そして動作確認用の ListGuesser コンポーネントを追加したら,すぐに Users 一覧画面を実装できた.その後は Datagrid コンポーネントなどを使って一覧画面をカスタマイズしていく❗️

marmelab.com

marmelab.com

Users 一覧画面

次に Posts 一覧画面を実装するときに ReferenceField コンポーネントが出てくる📝簡単に言うとリソース間の参照をしてくれて,今回だと Posts.user_idUsers.id に紐付けてくれる🔗 便利だ〜

marmelab.com

そして Create コンポーネントと SimpleForm コンポーネントを使って Posts 登録画面も簡単に実装できた.ちなみに React-Admin では「楽観的アップデート (optimistic updates)」という仕組みがあって,Save ボタンを押してから数秒後に API が呼び出されると書いてあった💡そして数秒以内であれば取り消すこともできる.Gmail の 元に戻す に似てる〜

marmelab.com

marmelab.com

Posts 登録画面

後半では Posts 一覧画面に検索とフィルタを実装したりもする🔎

Posts 一覧画面(検索とフィルタ)

終盤ではログイン画面も作る🔑今回は localStorage に設定する超簡易的な仕組みだけど,React-Admin には Auth0 / Amazon Cognito などの Auth Provider もあるから柔軟に実装できそう.

marmelab.com

ログイン画面

まとめ

「React-Admin Tutorial」は1時間ぐらいで React-Admin を速習できる素晴らしいコンテンツだった❗️基本は理解できたので,あとは他のドキュメントも読みつつ実装していこうと思う.ちなみにドキュメントに Beginner Mode というトグルがあるのもよく考えられていて良いな〜と思った👏

AWS CDK の --exclusively オプションでスタック間の依存関係を考慮せずに実行する

AWS CDK の cdk diff コマンド・cdk deploy コマンドで --exclusively オプションもしくは -e オプションを指定して実行すると,スタック間の依存関係を考慮せずに指定したスタックのみ操作できる👌実行時間を短くしたり,デプロイの影響範囲を狭めたりするときに使えたりする

-e, --exclusively
Only diff requested stacks, don't include dependencies [boolean]

docs.aws.amazon.com

docs.aws.amazon.com

--exclusively オプションなし(デフォルト)

stack.addDependency(stack) を使って InfrastructureBackendFrontend という依存関係のあるスタックを作る.あくまでサンプルとして❗️

sandboxCdkFrontendStack.addDependency(sandboxCdkBackendStack)
sandboxCdkBackendStack.addDependency(sandboxCdkInfrastructureStack)

そして Frontend (SandboxCdkFrontendStack) に対して cdk diff コマンドを実行すると,以下のように依存関係に従って実行できる.実行ログは抜粋している📝

$ cdk diff SandboxCdkFrontendStack
Including dependency stacks: SandboxCdkBackendStack, SandboxCdkInfrastructureStack

Stack SandboxCdkFrontendStack
(中略)
Stack SandboxCdkBackendStack
(中略)
Stack SandboxCdkInfrastructureStack
(中略)

cdk deploy コマンドも同じ.

$ cdk deploy SandboxCdkFrontendStack
Including dependency stacks: SandboxCdkBackendStack, SandboxCdkInfrastructureStack

SandboxCdkInfrastructureStack
SandboxCdkInfrastructureStack: deploying... [3/3]
(中略)

SandboxCdkBackendStack
SandboxCdkBackendStack: deploying... [2/3]
(中略)

SandboxCdkFrontendStack
SandboxCdkFrontendStack: deploying... [1/3]
(中略)

--exclusively オプションあり

今度は Frontend (SandboxCdkFrontendStack) に対して cdk diff --exclusively コマンドを実行すると,以下のように依存関係を考慮せずに指定したスタックのみ操作できる❗

$ cdk diff --exclusively SandboxCdkFrontendStack

Stack SandboxCdkFrontendStack
(中略)

cdk deploy コマンドも同じ.

$ cdk deploy --exclusively SandboxCdkFrontendStack

SandboxCdkFrontendStack: deploying... [1/1]
(中略)

Terraform で Amazon ECS の fargateTaskRetirementWaitPeriod を設定する

2024年5月17日にリリースされた Terraform AWS Provider v5.50.0 で Amazon ECS の「Fargate タスクリタイア待機時間 (fargateTaskRetirementWaitPeriod)」を設定できるようになった.

github.com

デフォルトでは「7日間」に設定されていて,選択肢としては 0日7日14日 を設定できる.基本的にはすぐにアップデートを反映しておくと良さそうだけど,Fargate タスクの停止に制約・課題がある場合は長めに設定しておくと良さそう.ちなみにブログ記事には 稀に重大なセキュリティアップデートがある場合は待機時間無しでリタイアさせる と書いてある📝

docs.aws.amazon.com

aws.amazon.com

さっそく試す

Amazon ECS の aws_ecs_account_setting_default リソースを使って設定する.以下の例では「Fargate タスクリタイア待機時間」0日 に設定している👌

resource "aws_ecs_account_setting_default" "main" {
  name  = "fargateTaskRetirementWaitPeriod"
  value = "0"
}

aws ecs list-account-setting コマンド

現時点ではマネジメントコンソールで fargateTaskRetirementWaitPeriod の設定を確認することができず,AWS CLI の aws ecs list-account-setting コマンドを使う必要がある👀

awscli.amazonaws.com

Before(terraform apply 実行前)

$ aws ecs list-account-settings --name fargateTaskRetirementWaitPeriod --effective-settings
{
    "settings": [
        {
            "name": "fargateTaskRetirementWaitPeriod",
            "value": "7",
            "principalArn": "arn:aws:iam::000000000000:root",
            "type": "user"
        }
    ]
}

After(terraform apply 実行後)

$ aws ecs list-account-settings --name fargateTaskRetirementWaitPeriod --effective-settings
{
    "settings": [
        {
            "name": "fargateTaskRetirementWaitPeriod",
            "value": "0",
            "principalArn": "arn:aws:iam::000000000000:root",
            "type": "user"
        }
    ]
}

AWS Step Functions の TestState API でステートをテストしよう!

AWS Step Functions ワークフローを実装しているときに,ステートをデプロイする前にテストしたり,デプロイしたワークフローの特定のステートのみを実行したいという場面があったりする.実は AWS Step Functions の「TestState API」を使うと,ワークフローをデプロイせずに1つのステートをテストできてスムーズに実装を進められる👌

ちなみに TestState API は2023年11月末にリリースされている❗️

aws.amazon.com

docs.aws.amazon.com

AWS CLI で TestState API を試す

AWS CLI の aws stepfunctions test-state コマンドを使えば TestState API を簡単に試せる.aws stepfunctions test-state コマンドの必須オプションは大きく2つ(--definition--role-arn)ある.--definition を指定するため,実際に AWS Step Functions ワークフローをデプロイする必要はなく,Amazon States Language (ASL) を直接指定してテストできる👌

awscli.amazonaws.com

1. Lambda Invoke を試す 🧩

実際に存在する AWS Lambda 関数を呼び出すステートを JSON で定義する📝

{
    "Type": "Task",
    "Resource": "arn:aws:states:::lambda:invoke",
    "OutputPath": "$.Payload",
    "Parameters": {
        "Payload.$": "$",
        "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:xxxxxxx:$LATEST"
    },
    "End": true
}

ステートのイメージは以下のような感じ.

そしてコマンドを実行すると指定したワークフロー定義から AWS Lambda 関数を呼び出せた👏

$ aws stepfunctions test-state \
    --definition file://state.json \
    --role-arn arn:aws:iam::000000000000:role/xxxxx \
    --input '{ "key1": "value1", "key2": "value2" }'
{
    "output": "{\"statusCode\":200,\"body\":\"\\\"Hello from Lambda!\\\"\"}",
    "status": "SUCCEEDED"
}

2. S3 PutObject を試す 🧩

次に AWS Step Functions の「SDK 統合」を使って,実際に存在する Amazon S3 バケットにオブジェクトを保存するステートを JSON で定義する📝

{
    "Type": "Task",
    "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
    "Parameters": {
        "Body.$": "$.body",
        "Bucket": "xxxxxxxxxx",
        "Key": "aws-stepfunctions-test-state.txt"
    },
    "End": true
}

ステートのイメージは以下のような感じ.

そしてコマンドを実行すると指定したワークフロー定義から Amazon S3 バケットにオブジェクトを保存できた👏

$ aws stepfunctions test-state \
    --definition file://state.json \
    --role-arn arn:aws:iam::000000000000:role/xxxxx \
    --input '{ "body": "hello!" }'
{
    "output": "{\"ETag\":\"\\\"1092c22d20edf737ded7bfb149dd8c9e\\\"\",\"ServerSideEncryption\":\"AES256\"}",
    "status": "SUCCEEDED"
}

3. Choice (NumericEquals) を試す 🧩

今度は AWS Step Functions の Choiceドキュメントに載っている NumericEquals のサンプルを参考に分岐のあるステートを JSON で定義する📝

{
    "Type": "Choice",
    "Choices": [
        {
            "Variable": "$.foo",
            "NumericEquals": 1,
            "Next": "FirstMatchState"
        }
    ],
    "Default": "Success"
}

ステートのイメージは以下のような感じ.

分岐をテストするために { "foo": 1 }{ "foo": 100 } を指定してコマンドを実行すると,期待した nextState になっていることを確認できた👏

$ aws stepfunctions test-state \
    --definition file://state.json \
    --role-arn arn:aws:iam::000000000000:role/xxxxx \
    --input '{ "foo": 1 }'
{
    "output": "{ \"foo\": 1 }",
    "nextState": "FirstMatchState",
    "status": "SUCCEEDED"
}

$ aws stepfunctions test-state \
    --definition file://state.json \
    --role-arn arn:aws:iam::000000000000:role/xxxxx \
    --input '{ "foo": 100 }'
{
    "output": "{ \"foo\": 100 }",
    "nextState": "Success",
    "status": "SUCCEEDED"
}

4. Choice (IsNull) を試す 🧩

もう一つ AWS Step Functions の Choiceドキュメントに載っている IsNull のサンプルを参考に分岐のあるステートを JSON で定義する📝

{
    "Type": "Choice",
    "Choices": [
        {
            "Variable": "$.possiblyNullValue",
            "IsNull": true,
            "Next": "Fail"
        }
    ],
    "Default": "Success"
}

ステートのイメージは以下のような感じ.

分岐をテストするために { "possiblyNullValue": 12345 }{} を指定してコマンドを実行すると,期待した nextState になっていることを確認できた👏

$ aws stepfunctions test-state \
    --definition file://state.json \
    --role-arn arn:aws:iam::000000000000:role/xxxxx \
    --input '{ "possiblyNullValue": 12345 }'
{
    "output": "{ \"possiblyNullValue\": 12345 }",
    "nextState": "Success",
    "status": "SUCCEEDED"
}

$ aws stepfunctions test-state \
    --definition file://state.json \
    --role-arn arn:aws:iam::000000000000:role/xxxxx \
    --input '{}'
{
    "error": "States.Runtime",
    "cause": "Invalid path '$.possiblyNullValue': The choice state's condition path references an invalid value.",
    "status": "FAILED"
}

マネジメントコンソールで TestState API を試す

TestState API は AWS Step Functions のマネジメントコンソールでも使える❗️

デプロイ前のワークフローであれば AWS Step Functions Workflow Studio で テスト状態 というボタンを押す.さらにデプロイして実行した AWS Step Functions ワークフローに対しても,特定のステートを選択して テスト状態 というボタンを押せば,インプットを変更して実行できる👌個人的には テスト状態 という日本語訳が微妙で気付きにくいように思う💨

デプロイして実行した AWS Step Functions ワークフローのトラブルシューティングに TestState API を使うことが多く,複雑なワークフローを開発しているときに特に便利だな〜と感じる❗️

TestState API を自動テストに活用する

AWS Compute Blog のブログ記事 Accelerating workflow development with the TestState API in AWS Step Functions を読んでいたら,Jest から TestState API を実行してレスポンスの nextStatestatus を確認する自動テストに活用する例が載っていて参考になった📝

aws.amazon.com

Python でツリー構造を表現できる treelib

Python ライブラリ treelib を使うと簡単にツリー構造を表現できる.今まで使ったことがなくて,ドキュメントを見ながら基本的な操作を試してみた🌴

treelib.readthedocs.io

github.com

ちなみに treelib は「AWS コンピュータービジョン開発の教科書」を読んでいたら,Amazon Rekognition のラベル検出結果をツリー構造で表示するために使われていて,本のトピックと直接は関係ないけど「こんなのあるんだ〜💡」と気になってしまった \( 'ω')/

kakakakakku.hatenablog.com

サンプル

今回はサンプルとして以下のようなツリー構造を treelib で作って,気になった操作を試してみる❗️サポートされてる全ての操作はドキュメント参照📝

root
├── A01
│   └── A11
├── B01
│   ├── B11
│   └── B12
│       ├── B121
│       └── B122
└── C01
    ├── C11
    ├── C12
    └── C13

ツリー構造を作る

以下のように Tree()create_node() でツリー構造を簡単に表現できる.子ノードを作るときは create_node()parent を指定すれば OK👌

from treelib import Tree

tree = Tree()

root = tree.create_node('root')
a01 = tree.create_node('A01', parent=root)
a11 = tree.create_node('A11', parent=a01)
b01 = tree.create_node('B01', parent=root)
b11 = tree.create_node('B11', parent=b01)
b12 = tree.create_node('B12', parent=b01)
b121= tree.create_node('B121', parent=b12)
b122= tree.create_node('B122', parent=b12)
c01 = tree.create_node('C01', parent=root)
c11 = tree.create_node('C11', parent=c01)
c12 = tree.create_node('C12', parent=c01)
c13 = tree.create_node('C13', parent=c01)

表示する

show() を使えばツリー構造をそのまま表示できる.

# root
# ├── A01
# │   └── A11
# ├── B01
# │   ├── B11
# │   └── B12
# │       ├── B121
# │       └── B122
# └── C01
#     ├── C11
#     ├── C12
#     └── C13
print(tree.show(stdout=False))

JSON に変換する

to_json() を使えばツリー構造を JSON に変換して表示できる.

# {"root": {"children": [{"A01": {"children": ["A11"]}}, {"B01": {"children": ["B11", {"B12": {"children": ["B121", "B122"]}}]}}, {"C01": {"children": ["C11", "C12", "C13"]}}]}}
print(tree.to_json())

JSON をフォーマットすると以下のようになる💡

{
    "root": {
        "children": [
            {
                "A01": {
                    "children": [
                        "A11"
                    ]
                }
            },
            {
                "B01": {
                    "children": [
                        "B11",
                        {
                            "B12": {
                                "children": [
                                    "B121",
                                    "B122"
                                ]
                            }
                        }
                    ]
                }
            },
            {
                "C01": {
                    "children": [
                        "C11",
                        "C12",
                        "C13"
                    ]
                }
            }
        ]
    }
}

Graphviz に変換する

to_graphviz() を使えば .dot ファイルに変換できる.

tree.to_graphviz('tree.dot')

ファイルに出力する

save2file() を使えばツリー構造をそのままファイルに出力できる.

tree.save2file('tree.txt')

深さを確認する

depth() を使えばツリー構造の深さを確認できる.

# 3
print(tree.depth())

子ノードを取得する

children() を使えば子ノードのリストを取得できる.

# node.tag='B121' node.identifier='cc4caccc-023e-11ef-bdee-5a033878925f'
# node.tag='B122' node.identifier='cc4cacfe-023e-11ef-bdee-5a033878925f'
b12_children = tree.children(b12.identifier)
for node in b12_children:
    print(f'{node.tag=} {node.identifier=}')

部分木を取得する

subtree() を使えば部分木を取得できる.

# B01
# ├── B11
# └── B12
#     ├── B121
#     └── B122
subtree_b01 = tree.subtree(b01.identifier)
print(subtree_b01.show(stdout=False))

ノードを削除する

remove_node() を使えばツリー構造からノードを削除できる.

# root
# ├── A01
# │   └── A11
# └── B01
#     ├── B11
#     └── B12
#         ├── B121
#         └── B122
tree.remove_node(c01.identifier)
print(tree.show(stdout=False))

関連記事

tree.nathanfriend.io を使うと「tree コマンド風の」ディレクトリ構造をウェブサイトで簡単に生成できて便利❗️

kakakakakku.hatenablog.com