TECH PLAY

電通総研

電通総研 の技術ブログ

822

こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Amazon DynamoDB を利用する時、取得できる属性を特定の属性のみに制限したいことがあったため、IAM ポリシーを利用して実現する方法をまとめておきます。 ユースケース としては、複数のアプリが同じ DynamoDB テーブルにアクセスするような構成において、特定のアプリには一部の属性しか見せたくないような場合です。 (あまり現実的ではないですが)簡単な例として、図のようにユーザー情報を保持する DynamoDB テーブルを「メール送信アプリ」と「データ分析アプリ」が利用しているとします。「メール送信アプリ」はユーザー名とメールアドレスだけを利用するのでそれ以外の属性は取得できないように制限をかけたいです。一方で「データ分析アプリ」は住所と ジェンダー だけを利用するので、それ以外の属性は取得できないように制限をかけたいです。 これはアプリに許可を与える IAM ポリシーの Condition 句を利用することで実現できます。 DynamoDB テーブルの作成 全属性の取得を許可する IAM ポリシーの付与 データ取得の確認1 (メイン)取得できる属性を制限する IAM ポリシーに修正 データ取得の確認2 まとめ DynamoDB テーブルの作成 まずは準備として図のように DynamoDB テーブルを作成します。 パーティション キーとして user_id を単独のプライマリーキーとし、サンプルデータを追加しておきます。 全属性の取得を許可する IAM ポリシーの付与 以下の IAM ポリシーでアプリケーションに GetItem と Scan の操作を許可します。取得できる属性を制限していないので、この状態では全属性を取得できるはずです。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " dynamodb:GetItem ", " dynamodb:Scan " ] , " Resource ": " arn:aws:dynamodb:*:*:table/users " } ] } データ取得の確認1 AWS SDK for JavaScript によるデータ取得のコードサンプルです。まずは特定のアイテムを取得する GetItem 操作です。 import { DynamoDBClient , GetItemCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new GetItemCommand ( { TableName: "users" , Key: { user_id: { S: "1ab24x" } } , } ); const result = await client.send ( command ); console .log ( result.Item ); アイテムの全属性を取得できました。 { user_id: { S: '1ab24x' }, address: { S: '不思議の国X市' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' }, gender: { S: 'F' } } 次に、全アイテムを取得する Scan 操作です。 import { DynamoDBClient , ScanCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new ScanCommand ( { TableName: "users" , } ); const result = await client.send ( command ); console .log ( result.Items ); こちらもアイテムの全属性を取得できました。 [ { user_id: { S: 'k4p1c3' }, address: { S: '鏡の国Y町' }, email: { S: 'bob@wonderland.com' }, user_name: { S: 'Bob' }, gender: { S: 'M' } }, { user_id: { S: '1ab24x' }, address: { S: '不思議の国X市' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' }, gender: { S: 'F' } } ] (メイン)取得できる属性を制限する IAM ポリシーに修正 IAM ポリシーを修正し、明示的に許可した属性しか取得できないようにしていきます。以下のドキュメントを参考にします。 詳細に設定されたアクセスコントロールのための IAM ポリシー条件の使用 Condition 句に dynamodb:Attributes と dynamodb:Select を追加します。 dynamodb:Attributes には取得を許可したい属性名を配列で指定します。これにはプライマリーキーが含まれている必要があります。 dynamodb:Select には SPECIFIC_ATTRIBUTES と記載します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " dynamodb:GetItem ", " dynamodb:Scan " ] , " Resource ": " arn:aws:dynamodb:*:*:table/users ", " Condition ": { " ForAllValues:StringEquals ": { " dynamodb:Attributes ": [ " user_id ", " user_name ", " email " ] } , " StringEqualsIfExists ": { " dynamodb:Select ": " SPECIFIC_ATTRIBUTES " } } } ] } データ取得の確認2 IAM ポリシーを変更した状態で前と同じ GetItem 操作を行うと、次のようなエラーになりました。 AccessDeniedException: User: arn: aws :iam::111122223333:user/<ユーザ名> is not authorized to perform: dynamodb:GetItem on resource: arn: aws :dynamodb:ap-northeast-1:111122223333:table/users because no identity-based policy allows the dynamodb:GetItem action dynamodb:GetItem が許可されていないというメッセージですが、実際には許可されていない属性を取得しようとしているためエラーが発生した状況です。次のように プロジェクション式 を利用して、許可された属性のみを取得するようにコマンドを変更します。 import { DynamoDBClient , GetItemCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new GetItemCommand ( { TableName: "users" , Key: { user_id: { S: "1ab24x" } } , ProjectionExpression: "user_id, user_name, email" , // これを追加 } ); const result = await client.send ( command ); console .log ( result.Item ); こうするとエラーが解消し、結果も許可された属性のみが返却されていることがわかります。 { user_id: { S: '1ab24x' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' } } プロジェクション式に許可されていない属性( address など)が含まれていると、前述と同じエラーが発生します。 Scan 操作についても、プロジェクション式を追加して取得する属性を明示的に指定することで、エラーなくスキャン操作ができます。 import { DynamoDBClient , ScanCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new ScanCommand ( { TableName: "users" , ProjectionExpression: "user_id, user_name, email" , // これを追加 } ); const result = await client.send ( command ); console .log ( result.Items ); 許可された属性のみが返却されます。 [ { user_id: { S: 'k4p1c3' }, email: { S: 'bob@wonderland.com' }, user_name: { S: 'Bob' } }, { user_id: { S: '1ab24x' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' } } ] まとめ Condition 句に dynamodb:Attributes と dynamodb:Select を利用することで、特定の属性のみ取得できるような IAM ポリシーの作り方をまとめました。アイテムを取得する際にはプロジェクション式を利用する必要があるので、どの属性の取得が許可されているのか アプリ開発 側でも知っておく必要があります。 執筆: @kou.kinyo 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Amazon DynamoDB を利用する時、取得できる属性を特定の属性のみに制限したいことがあったため、IAM ポリシーを利用して実現する方法をまとめておきます。 ユースケース としては、複数のアプリが同じ DynamoDB テーブルにアクセスするような構成において、特定のアプリには一部の属性しか見せたくないような場合です。 (あまり現実的ではないですが)簡単な例として、図のようにユーザー情報を保持する DynamoDB テーブルを「メール送信アプリ」と「データ分析アプリ」が利用しているとします。「メール送信アプリ」はユーザー名とメールアドレスだけを利用するのでそれ以外の属性は取得できないように制限をかけたいです。一方で「データ分析アプリ」は住所と ジェンダー だけを利用するので、それ以外の属性は取得できないように制限をかけたいです。 これはアプリに許可を与える IAM ポリシーの Condition 句を利用することで実現できます。 DynamoDB テーブルの作成 全属性の取得を許可する IAM ポリシーの付与 データ取得の確認1 (メイン)取得できる属性を制限する IAM ポリシーに修正 データ取得の確認2 まとめ DynamoDB テーブルの作成 まずは準備として図のように DynamoDB テーブルを作成します。 パーティション キーとして user_id を単独のプライマリーキーとし、サンプルデータを追加しておきます。 全属性の取得を許可する IAM ポリシーの付与 以下の IAM ポリシーでアプリケーションに GetItem と Scan の操作を許可します。取得できる属性を制限していないので、この状態では全属性を取得できるはずです。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " dynamodb:GetItem ", " dynamodb:Scan " ] , " Resource ": " arn:aws:dynamodb:*:*:table/users " } ] } データ取得の確認1 AWS SDK for JavaScript によるデータ取得のコードサンプルです。まずは特定のアイテムを取得する GetItem 操作です。 import { DynamoDBClient , GetItemCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new GetItemCommand ( { TableName: "users" , Key: { user_id: { S: "1ab24x" } } , } ); const result = await client.send ( command ); console .log ( result.Item ); アイテムの全属性を取得できました。 { user_id: { S: '1ab24x' }, address: { S: '不思議の国X市' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' }, gender: { S: 'F' } } 次に、全アイテムを取得する Scan 操作です。 import { DynamoDBClient , ScanCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new ScanCommand ( { TableName: "users" , } ); const result = await client.send ( command ); console .log ( result.Items ); こちらもアイテムの全属性を取得できました。 [ { user_id: { S: 'k4p1c3' }, address: { S: '鏡の国Y町' }, email: { S: 'bob@wonderland.com' }, user_name: { S: 'Bob' }, gender: { S: 'M' } }, { user_id: { S: '1ab24x' }, address: { S: '不思議の国X市' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' }, gender: { S: 'F' } } ] (メイン)取得できる属性を制限する IAM ポリシーに修正 IAM ポリシーを修正し、明示的に許可した属性しか取得できないようにしていきます。以下のドキュメントを参考にします。 詳細に設定されたアクセスコントロールのための IAM ポリシー条件の使用 Condition 句に dynamodb:Attributes と dynamodb:Select を追加します。 dynamodb:Attributes には取得を許可したい属性名を配列で指定します。これにはプライマリーキーが含まれている必要があります。 dynamodb:Select には SPECIFIC_ATTRIBUTES と記載します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " dynamodb:GetItem ", " dynamodb:Scan " ] , " Resource ": " arn:aws:dynamodb:*:*:table/users ", " Condition ": { " ForAllValues:StringEquals ": { " dynamodb:Attributes ": [ " user_id ", " user_name ", " email " ] } , " StringEqualsIfExists ": { " dynamodb:Select ": " SPECIFIC_ATTRIBUTES " } } } ] } データ取得の確認2 IAM ポリシーを変更した状態で前と同じ GetItem 操作を行うと、次のようなエラーになりました。 AccessDeniedException: User: arn: aws :iam::111122223333:user/<ユーザ名> is not authorized to perform: dynamodb:GetItem on resource: arn: aws :dynamodb:ap-northeast-1:111122223333:table/users because no identity-based policy allows the dynamodb:GetItem action dynamodb:GetItem が許可されていないというメッセージですが、実際には許可されていない属性を取得しようとしているためエラーが発生した状況です。次のように プロジェクション式 を利用して、許可された属性のみを取得するようにコマンドを変更します。 import { DynamoDBClient , GetItemCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new GetItemCommand ( { TableName: "users" , Key: { user_id: { S: "1ab24x" } } , ProjectionExpression: "user_id, user_name, email" , // これを追加 } ); const result = await client.send ( command ); console .log ( result.Item ); こうするとエラーが解消し、結果も許可された属性のみが返却されていることがわかります。 { user_id: { S: '1ab24x' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' } } プロジェクション式に許可されていない属性( address など)が含まれていると、前述と同じエラーが発生します。 Scan 操作についても、プロジェクション式を追加して取得する属性を明示的に指定することで、エラーなくスキャン操作ができます。 import { DynamoDBClient , ScanCommand } from "@aws-sdk/client-dynamodb" ; const client = new DynamoDBClient ( {} ); const command = new ScanCommand ( { TableName: "users" , ProjectionExpression: "user_id, user_name, email" , // これを追加 } ); const result = await client.send ( command ); console .log ( result.Items ); 許可された属性のみが返却されます。 [ { user_id: { S: 'k4p1c3' }, email: { S: 'bob@wonderland.com' }, user_name: { S: 'Bob' } }, { user_id: { S: '1ab24x' }, email: { S: 'alice@wonderland.com' }, user_name: { S: 'Alice' } } ] まとめ Condition 句に dynamodb:Attributes と dynamodb:Select を利用することで、特定の属性のみ取得できるような IAM ポリシーの作り方をまとめました。アイテムを取得する際にはプロジェクション式を利用する必要があるので、どの属性の取得が許可されているのか アプリ開発 側でも知っておく必要があります。 執筆: @kou.kinyo 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
みなさんこんにちは、X(クロス) イノベーション 本部 ソフトウェアデザインセンターの鈴木です。 12月も折り返しを迎えて、今年も終わりが近づいていますね。 電通国際情報サービス Advent Calendar 2023 の15日目の投稿です。 この度2023年3月から9ヶ月かけて開発した、社内の GitHub やJiraのアカウント・権限管理を行う社内アプリケーション「Icarus」のリリースを無事迎えられました。本記事ではプロジェクトの内容と本開発で得られた経験をまとめていきたいと思います。 前半では開発についてまとめます。開発の背景、技術スタック、開発体制や開発ツールについて紹介します。 後半では新卒3年目で初めて、開発案件に アサイ ンされた筆者目線で本案件を振り返ります。本案件に配属される前は、主に管理系の仕事をメインに働いていましたが、開発案件への異動希望を上長に伝えて開発系の本案件に アサイ ンしていただきました。そのため、本開発への期待と得られた学びや経験をまとめていきたいと思います。 開発について 開発の背景と概要 技術スタック 開発スケジュール 開発体制 本案件の振り返り Icarus開発へアサインされた経緯 期待と得られた経験 1. フルサイクルエンジニアとしての知識と経験の獲得 2. AWSの基礎知識とWeb3層アーキテクチャの実装経験の獲得 3. CI/CDやIaCなどの開発効率化に関する知識と経験の獲得 まとめ 開発について 開発の背景と概要 私の会社では社内標準の開発ツールとして GitHub とJiraがあり、そのアカウント・権限管理を行う社内アプリケーションがもともとありました。しかし、従来の社内アプリケーションは以下2つの問題があり、「Icarus」はその問題を解決するためのリプレース開発としてスタートしました。 データベースには Amazon DynamoDB (NoSQL型データベース)を採用していた。しかし、利用ユーザーの増加や パーティション キー以外での検索要件が出てきたことでフルスキャンをせざるを得ないケースがたびたび発生した。それによりレスポンスタイムやコストが悪化していた。 → データベースをリレーショナルデータベースに変更する。 アプリケーションは AWS 上に構築されていたが開発の経緯上、利用者向けアプリケーションと管理者用アプリケーションで別々の VPC 上に構築されていたことで余分なコストがかかっていた。 → 利用者向けアプリケーションと管理者用アプリケーションを統合する。 また開発メンバーは比較的若手で構成されており、フルサイクルエンジニアとしての知見獲得・ スキルアップ の機会としても期待されていました。 以上のようにアプリケーションの問題を解決してフルサイクルエンジニアとして成長をするため、フロント部分を含めてアプリケーションを刷新することとなりました。 技術スタック 主要な技術スタックは以下のとおりです。 フロントエンド・バックエンド Next.js React TypeScript Axios データベース PostgreSQL インフラ( AWS ) (可能なものは AWS CDKでIaC化しました) Amazon Route53 Elastic Load Balancing Application Load Balancer AWS WAF Amazon ECS + AWS Fargate Amazon RDS CI/CD GitHub Actions AWS CodeDeploy(ブルー/グリーンデプロイ用) 開発スケジュール 開発スケジュールはおおまかに以下のとおりです。 ちなみに私は2023年3月から本プロジェクトに参画しました。 開発体制 初期開発メンバーは比較的若手の社員4名で構成されました。 (7月ごろから体制が縮小され、2名で開発していました) 開発手法としては スクラム 開発を採用しており、2週間のスプリントで開発・テスト・リリースを繰り返していました。 本案件の振り返り 冒頭でも記載した通り、本案件は希望を出して アサイ ンさせていただきました。さらに幸運なことに、配属先の部署は全社的な技術力を牽引するトップクラスの技術者が集まるこの上ない環境でした。 以降では、私がなぜ開発案件を希望したかと本開発で得られた経験と学びについて記載させていただきます。 Icarus開発へ アサイ ンされた経緯 私は新卒入社後の初期配属として開発案件への配属を希望していました。理由としては、今後のキャリアを考えた上で自分でシステムを開発した経験をしておきたかったからです。 後々はアーキテクトやPMでキャリアを積んでいきたいと考えており、そのためには自分で動くものを実案件で実装したかったという思いがあります。実際に手を動かした経験があることで、対内・対外問わず説明に説得力を持たせられたり精巧な アーキテクチャ 設計ができたりするのではないかと考えているからです。 学校の部活で例えると、学生時代に野球を全くしていなかった顧問より、学生時代に野球部に所属していた顧問の方が指導に説得力や効果がありそうということです。(もちろん学生時代に野球を全くしていなかった人でも素晴らしい指導者はいますが) しかし事業部の都合もあり、初期配属の希望は叶わず管理系をメインとする案件に アサイ ンされることとなりました。もちろんそのプロジェクトでも大きな学びはあったのですが、やはり若手のうちに開発経験を積んでおきたいという意向を上長に伝え続けた結果、「Icarus」の開発案件に アサイ ンしていただけることとなりました。 もちろん事業部の状況や都合もありますが、社員の意向を叶えようとしていただけるとても良い会社だと思いました。 期待と得られた経験 本案件へ参画するにあたり、個人的に以下の期待がありました。 WEBアプリケーションをフロントエンド・バックエンド・インフラ問わず全般的に開発できる、フルサイクルエンジニアとしての知識と経験の獲得 AWS の基礎知識とWeb3層 アーキテクチャ の実装経験の獲得 CI/CDやIaCなどの開発効率化に関する知識と経験の獲得 以上3点の期待に対する結果をまとめます。 1. フルサイクルエンジニアとしての知識と経験の獲得 「Icarus」の開発では機能単位で開発を行っていたため、フロントエンドとバックエンドを一貫して実装することができました。またインフラの実装全般を担当させていただき、 AWS を用いたインフラ実装も経験することができました。 アプリケーションを全体的に実装することで、どのようにWebアプリケーションは動作するのかをより深く理解することができました。 また開発の過程で同じ部署の方々に指導していただき、セキュリティ周りや低レイヤーの知見、技術者としての心構えなどたくさんのことを教えていただきました。 2. AWS の基礎知識とWeb3層 アーキテクチャ の実装経験の獲得 以前入っていた案件では主にフロント側を担当しており、 クラウド の知見を獲得するため AWS によるインフラ実装を経験したかった次第です。 クラウド 技術が可用性やコストの面で強力な技術であることは知識としては知っていましたが、実際に学習・実装してみると身に沁みて強力さを実感しました。実装してみて特に強力だと思ったのは以下の3点です。 ELBや AWS Fargateや Amazon RDSなど AWS のサービスを用いることで、簡単に高可用構成を実装できる。 マネージドサービスを活用することでサーバー管理などが不要になり、開発に集中できる。 AWS CDKや AWS CloudFormationを活用することでインフラをコード化でき、構成管理が可能で環境の展開・削除が迅速に行えるようになる。 また、 AWS 知識を広げる中で「 AWS Certified Solutions Architect - Professional」の資格を取得することができました。合格体験記を作成したので興味があればお読みください。 AWS Certified Solutions Architect - Professional AWS は本当に多くのサービスがあり、いろいろなサービスを試したくなりました。特に学習する中で AWS Lambda+ Amazon DynamoDB などのサーバーレス アーキテクチャ 構成に興味が湧いたため、個人的に実装してみようと思いました。 3. CI/CDやIaCなどの開発効率化に関する知識と経験の獲得 効率的でモダンな開発をする上でCI/CDやIaCは必要だと認識していました。特にCI/CDについては以前担当していた案件で、CIで自動テストが実装されていなかったことによる ソースコード 修正のハードルの高さであったり、CDが実装されていなかったことによる手動デプロイによる操作ミスがあったりと必要性を感じていました。 CIについては 単体テスト をJestというテスト フレームワーク で実装して、 GitHub Actionsで リポジトリ へプッシュした際に自動テストを回しています。それによりバグ発生のリスクを極力抑えながら迅速に ソースコード を修正することができました。 CDについては、 GitHub Actionsで AWS 環境へのデプロイを自動化しており、操作ミスを減らして迅速なデプロイができるパイプラインを整備しました。 IaCについては、 AWS CDKでインフラをコード化して AWS 環境の迅速な作成を可能としていました。特に実感したのは本番環境の作成時で、検証環境作成時に AWS CDKでIaC化していたため、本番環境の作成は調整やデプロイパイプラインの実装を含めて一人で4~5日程度で完了することができました。 このように自動化やコード化できるものは各種ツールを活用することで、開発効率をかなり向上させることができ、人的ミスを減らすことができるのだと身をもって理解しました。 まとめ 今回は、社内アプリケーション「Icarus」開発についてとそこから得られた学びや経験をまとめました。 記事の途中でも記載した通り、本案件の アサイ ンを通じて社員の意向を叶えようとしていただけるとても良い会社だと改めて感じました。また、ご指導いただいた配属先の方々には大変お世話になりました。 この開発から技術者として知識面・経験面ともに、フルサイクルエンジニアとして大きな成長を遂げることができました。今後はこの知識と経験を活かして顧客の理想を叶える システム開発 ができるよう、一層精進していこうと思います。 最後までお読みいただきありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! フルサイクルエンジニア 執筆: @suzuki.takuma 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
みなさんこんにちは、X(クロス) イノベーション 本部 ソフトウェアデザインセンターの鈴木です。 12月も折り返しを迎えて、今年も終わりが近づいていますね。 電通国際情報サービス Advent Calendar 2023 の15日目の投稿です。 この度2023年3月から9ヶ月かけて開発した、社内の GitHub やJiraのアカウント・権限管理を行う社内アプリケーション「Icarus」のリリースを無事迎えられました。本記事ではプロジェクトの内容と本開発で得られた経験をまとめていきたいと思います。 前半では開発についてまとめます。開発の背景、技術スタック、開発体制や開発ツールについて紹介します。 後半では新卒3年目で初めて、開発案件に アサイ ンされた筆者目線で本案件を振り返ります。本案件に配属される前は、主に管理系の仕事をメインに働いていましたが、開発案件への異動希望を上長に伝えて開発系の本案件に アサイ ンしていただきました。そのため、本開発への期待と得られた学びや経験をまとめていきたいと思います。 開発について 開発の背景と概要 技術スタック 開発スケジュール 開発体制 本案件の振り返り Icarus開発へアサインされた経緯 期待と得られた経験 1. フルサイクルエンジニアとしての知識と経験の獲得 2. AWSの基礎知識とWeb3層アーキテクチャの実装経験の獲得 3. CI/CDやIaCなどの開発効率化に関する知識と経験の獲得 まとめ 開発について 開発の背景と概要 私の会社では社内標準の開発ツールとして GitHub とJiraがあり、そのアカウント・権限管理を行う社内アプリケーションがもともとありました。しかし、従来の社内アプリケーションは以下2つの問題があり、「Icarus」はその問題を解決するためのリプレース開発としてスタートしました。 データベースには Amazon DynamoDB (NoSQL型データベース)を採用していた。しかし、利用ユーザーの増加や パーティション キー以外での検索要件が出てきたことでフルスキャンをせざるを得ないケースがたびたび発生した。それによりレスポンスタイムやコストが悪化していた。 → データベースをリレーショナルデータベースに変更する。 アプリケーションは AWS 上に構築されていたが開発の経緯上、利用者向けアプリケーションと管理者用アプリケーションで別々の VPC 上に構築されていたことで余分なコストがかかっていた。 → 利用者向けアプリケーションと管理者用アプリケーションを統合する。 また開発メンバーは比較的若手で構成されており、フルサイクルエンジニアとしての知見獲得・ スキルアップ の機会としても期待されていました。 以上のようにアプリケーションの問題を解決してフルサイクルエンジニアとして成長をするため、フロント部分を含めてアプリケーションを刷新することとなりました。 技術スタック 主要な技術スタックは以下のとおりです。 フロントエンド・バックエンド Next.js React TypeScript Axios データベース PostgreSQL インフラ( AWS ) (可能なものは AWS CDKでIaC化しました) Amazon Route53 Elastic Load Balancing Application Load Balancer AWS WAF Amazon ECS + AWS Fargate Amazon RDS CI/CD GitHub Actions AWS CodeDeploy(ブルー/グリーンデプロイ用) 開発スケジュール 開発スケジュールはおおまかに以下のとおりです。 ちなみに私は2023年3月から本プロジェクトに参画しました。 開発体制 初期開発メンバーは比較的若手の社員4名で構成されました。 (7月ごろから体制が縮小され、2名で開発していました) 開発手法としては スクラム 開発を採用しており、2週間のスプリントで開発・テスト・リリースを繰り返していました。 本案件の振り返り 冒頭でも記載した通り、本案件は希望を出して アサイ ンさせていただきました。さらに幸運なことに、配属先の部署は全社的な技術力を牽引するトップクラスの技術者が集まるこの上ない環境でした。 以降では、私がなぜ開発案件を希望したかと本開発で得られた経験と学びについて記載させていただきます。 Icarus開発へ アサイ ンされた経緯 私は新卒入社後の初期配属として開発案件への配属を希望していました。理由としては、今後のキャリアを考えた上で自分でシステムを開発した経験をしておきたかったからです。 後々はアーキテクトやPMでキャリアを積んでいきたいと考えており、そのためには自分で動くものを実案件で実装したかったという思いがあります。実際に手を動かした経験があることで、対内・対外問わず説明に説得力を持たせられたり精巧な アーキテクチャ 設計ができたりするのではないかと考えているからです。 学校の部活で例えると、学生時代に野球を全くしていなかった顧問より、学生時代に野球部に所属していた顧問の方が指導に説得力や効果がありそうということです。(もちろん学生時代に野球を全くしていなかった人でも素晴らしい指導者はいますが) しかし事業部の都合もあり、初期配属の希望は叶わず管理系をメインとする案件に アサイ ンされることとなりました。もちろんそのプロジェクトでも大きな学びはあったのですが、やはり若手のうちに開発経験を積んでおきたいという意向を上長に伝え続けた結果、「Icarus」の開発案件に アサイ ンしていただけることとなりました。 もちろん事業部の状況や都合もありますが、社員の意向を叶えようとしていただけるとても良い会社だと思いました。 期待と得られた経験 本案件へ参画するにあたり、個人的に以下の期待がありました。 WEBアプリケーションをフロントエンド・バックエンド・インフラ問わず全般的に開発できる、フルサイクルエンジニアとしての知識と経験の獲得 AWS の基礎知識とWeb3層 アーキテクチャ の実装経験の獲得 CI/CDやIaCなどの開発効率化に関する知識と経験の獲得 以上3点の期待に対する結果をまとめます。 1. フルサイクルエンジニアとしての知識と経験の獲得 「Icarus」の開発では機能単位で開発を行っていたため、フロントエンドとバックエンドを一貫して実装することができました。またインフラの実装全般を担当させていただき、 AWS を用いたインフラ実装も経験することができました。 アプリケーションを全体的に実装することで、どのようにWebアプリケーションは動作するのかをより深く理解することができました。 また開発の過程で同じ部署の方々に指導していただき、セキュリティ周りや低レイヤーの知見、技術者としての心構えなどたくさんのことを教えていただきました。 2. AWS の基礎知識とWeb3層 アーキテクチャ の実装経験の獲得 以前入っていた案件では主にフロント側を担当しており、 クラウド の知見を獲得するため AWS によるインフラ実装を経験したかった次第です。 クラウド 技術が可用性やコストの面で強力な技術であることは知識としては知っていましたが、実際に学習・実装してみると身に沁みて強力さを実感しました。実装してみて特に強力だと思ったのは以下の3点です。 ELBや AWS Fargateや Amazon RDSなど AWS のサービスを用いることで、簡単に高可用構成を実装できる。 マネージドサービスを活用することでサーバー管理などが不要になり、開発に集中できる。 AWS CDKや AWS CloudFormationを活用することでインフラをコード化でき、構成管理が可能で環境の展開・削除が迅速に行えるようになる。 また、 AWS 知識を広げる中で「 AWS Certified Solutions Architect - Professional」の資格を取得することができました。合格体験記を作成したので興味があればお読みください。 AWS Certified Solutions Architect - Professional AWS は本当に多くのサービスがあり、いろいろなサービスを試したくなりました。特に学習する中で AWS Lambda+ Amazon DynamoDB などのサーバーレス アーキテクチャ 構成に興味が湧いたため、個人的に実装してみようと思いました。 3. CI/CDやIaCなどの開発効率化に関する知識と経験の獲得 効率的でモダンな開発をする上でCI/CDやIaCは必要だと認識していました。特にCI/CDについては以前担当していた案件で、CIで自動テストが実装されていなかったことによる ソースコード 修正のハードルの高さであったり、CDが実装されていなかったことによる手動デプロイによる操作ミスがあったりと必要性を感じていました。 CIについては 単体テスト をJestというテスト フレームワーク で実装して、 GitHub Actionsで リポジトリ へプッシュした際に自動テストを回しています。それによりバグ発生のリスクを極力抑えながら迅速に ソースコード を修正することができました。 CDについては、 GitHub Actionsで AWS 環境へのデプロイを自動化しており、操作ミスを減らして迅速なデプロイができるパイプラインを整備しました。 IaCについては、 AWS CDKでインフラをコード化して AWS 環境の迅速な作成を可能としていました。特に実感したのは本番環境の作成時で、検証環境作成時に AWS CDKでIaC化していたため、本番環境の作成は調整やデプロイパイプラインの実装を含めて一人で4~5日程度で完了することができました。 このように自動化やコード化できるものは各種ツールを活用することで、開発効率をかなり向上させることができ、人的ミスを減らすことができるのだと身をもって理解しました。 まとめ 今回は、社内アプリケーション「Icarus」開発についてとそこから得られた学びや経験をまとめました。 記事の途中でも記載した通り、本案件の アサイ ンを通じて社員の意向を叶えようとしていただけるとても良い会社だと改めて感じました。また、ご指導いただいた配属先の方々には大変お世話になりました。 この開発から技術者として知識面・経験面ともに、フルサイクルエンジニアとして大きな成長を遂げることができました。今後はこの知識と経験を活かして顧客の理想を叶える システム開発 ができるよう、一層精進していこうと思います。 最後までお読みいただきありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! フルサイクルエンジニア 執筆: @suzuki.takuma 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
はじめに こんにちは。コミュニケーションIT事業部の石田です。 普段はソフトウェアエンジニアとして iPLAss というローコード開発プラットフォームの開発を担当しています。本記事は 電通国際情報サービス Advent Calendar 2023 の12/14の投稿です。 iPLAssとは、コーディング量を最小限に抑えた迅速なアプリケーション開発を実現するプラットフォームです。ブラウザ操作によるノンプログラミングでのデータ定義やその他各種定義(認証ポリシーや画面表示設定など)を行うことでアプリケーションを構築していくことが可能です。 また、データ管理画面の自動生成、認証/認可、ID連携、帳票出力、通知、カスタムロジック組込、WebAPI開発といったアプリケーションの開発に必要となる機能をデフォルトで提供しています。 iPLAssの機能の一つに、Web上に公開されるコンシューマ向けサイトを会員制サイト化し、iPLAss上で会員管理を行うことを可能とするWAM機能があります。本記事では、WAM機能のエンハンスとして、 CDN エッジ上の JavaScript ランタイムで動作するポータブル(=Node.jsやWorkersなどあらゆる JavaScript ランタイムで動作する)な認証・認可プロキシを実装した話を紹介します。 WAM機能とは まずは、冒頭で述べたiPLAssのWAM機能について簡単に説明します。 WAMは Web Account Manager の略であり、その名の通り、Web上に公開される任意のコンシューマ向けサイトを会員制サイト化し、iPLAss上で会員管理を行うことを可能とする機能です。 ※ 記事公開時点において、WAM機能は エンタープライズ 版限定で提供している機能になります。 WAM機能を使用することで、コンシューマ向けサイト側には手を加えずとも、会員制サイト化が可能になります。また、既存の会員制サイトについて、会員管理機能のみをiPLAssに置き換えて、その他は既存サイト(コンテンツ)を再利用するといったことも可能です。 WAM機能はあくまでiPLAssの機能の一つであり、iPLAssが具備するその他の豊富な機能と組み合わせることにより、あらゆるニーズ・ ユースケース に対応できます。例えば、ヘッドレス CMS のようなコンテンツ管理システムが一般的に具備している機能と同等なデータ管理画面(コンテンツ管理)、データの有効期間ベースのバージョン管理(コンテンツのバージョン管理・予約投稿)、ワークフロー(承認ワークフロー)、監査ログ記録、WebAPI開発といった機能も提供しており、iPLAss上でコンテンツ管理も併せて行ってしまうといったことも可能です。 WAM機能は、以下2つのモジュールから構成されます。 WAMモジュール iPLAssの本体サーバー( Java / Java EE )上で動作し、会員制サイトの会員管理を行うモジュールです。会員管理機能として、認証機能(ログイン・ログアウト)、サイトコンテンツの閲覧権限チェック機能、パスワード リカバリ 、ユーザー情報のセルフメンテナンス機能などを提供しています。 WAM プラグイン モジュール HTTPサーバー上でプロキシとして動作し、サイトコンテンツへのアクセスをフックしてコンテンツ閲覧制御を行うモジュールです。WAMモジュールとHTTP連携し、閲覧権限に応じたアクセス制御、未ログインユーザーのログイン画面へのリダイレクト、認証成功時のアクセス トーク ン受け渡しなどを行います。 これまでは Apache HTTP Server 、 IIS 、 JavaEE 上で動作するWAM プラグイン モジュールを提供してきましたが、今回新たに CDN エッジ上の JavaScript ランタイムで動作する前提で実装されたServerless( JavaScript )版の提供を開始しました。 CDN の前段でエッジプロキシとして動作させて認証・認可チェックを行うことで、 CDN によるコンテンツキャッシュを最大限活用することが可能になっています。 ここからは、新たに開発したServerless( JavaScript )版の全体構成や実装概要について説明します。 全体構成図 以下が、全体構成のイメージ図になります。 技術構成 実装概要 認証・認可プロキシのコアロジックはライブラリ化し、プライベートnpm リポジトリ からnpmパッケージとして利用者に配布しています。併せて、各 CDN エッジランタイムとのアダプターとなるnpmプロジェクトのひな形をいくつか提供するという形をとっています。「ポータブル」を意識し、コアロジックの実装では、特定のランタイムに依存しない API のみを使用しています(Fetch API など)。 ひな形プロジェクトでは、配布するnpmパッケージ内の API (コアロジック)を利用する形で、各 CDN エッジランタイムのハンドラー関数をデフォルトで実装しています。ハンドラー関数の他にも、認証・認可プロキシのアプリケーション設定およびカスタマイズのベースをあらかじめ実装しており、利用者が プログラマブル に設定を行ったり、カスタムロジックを追加したりできるようにしています。 例えば、アプリケーション設定については、ハードコードする以外の選択肢として、 環境変数 を埋め込んだり、Lambda@Edgeの場合には AWS AppConfigやParameter Storeといったサービスを利用するなどの選択肢を取ることが可能です。カスタマイズについては、コアロジック自体をカスタマイズすることや、フックして処理を追加することが可能であり、オリジナルのHTTPリク エス トを改変する(リク エス トヘッダーの追加、クエリパラメータの追加など)といったカスタマイズが可能です。 また、各 CDN エッジランタイムのビルド・デプロイ用のnpm スクリプト などもデフォルトで実装しており、利用者がアプリケーション設定とカスタマイズ実装にフォーカスできるようにしています。 プライベートnpm リポジトリ プライベートなnpm リポジトリ には Sonatype Nexus Repository の OSS 版を利用しています。構築した Nexus Repositoryはプライベートな Maven リポジトリ としての役割も兼ねており、iPLAss エンタープライズ 版の Java モジュールの配布にも利用しています。 Nexus Repositoryは OSS 版で Maven リポジトリ とnpm リポジトリ の両方に対応しており、とても重宝しています。 CDN エッジ(ランタイム) 現在、npmプロジェクトのひな形を配布している CDN エッジランタイムサービスは以下の3つです。 Cloudflare Workers Lambda@Edge Netlify Edge Functions それぞれのサービスの詳細説明はここでは割愛しますが、Workers、Node.js、Denoとあらゆる JavaScript ランタイムで実際に動作することが確認できています。今後も CDN エッジランタイムのトレンドを追いながら対応環境を拡大していきたいと考えています。 Webフレームワーク Webフレームワークとして、 unjs/h3 を採用しています。 h3は、 Nuxt 3 のサーバーエンジンの基となっているWebフレームワークです。 UnJS と呼ばれる JavaScript ライブラリ、ツール、ユーティリティ群を開発するプロジェクト(Nuxt開発チームを主導していたPooya Parsa氏が開発リーダーを務める)によって提供されているライブラリの一つです。 ミニマルかつポータブルを特徴とした高速なWebフレームワークであり、「 CDN エッジ上のあらゆる JavaScript ランタイムで動作させる」という今回の構想にマッチすると判断しました。また、Nuxt 3で利用されているライブラリであるということもあり、信頼性が高いという点も採用理由として挙げられます。個人的にVue.js/Nuxt.jsが好きで、Nuxt 3のキャッチアップをしているときにUnJSに出会いました(Vue.js/Nuxt.js好きというバイアスがかかっていることも否定できません)。 h3では、v1.8.0リリース時に Towards the Edge of the Web! を掲げており、Node.js環境でHTTPサーバーとして動作させる以外に、直接h3を使用するための2つのアダプタ(fetch互換の シグネチャ を持つWebAdapter、それ以外のプレーンな入出力オブジェクトを使用するPlainAdapter)が提供されました。これにより、Cloudflare WorkersやLambda@Edgeといった CDN エッジランタイムでh3を直接使用することが容易になりました(従来は、 unjs/nitro を使用するか、 unjs/unenv と組み合わせたカスタムコードを実装するしかありませんでした)。今回実装した認証・認可プロキシにおいても、各 CDN エッジランタイムのハンドラー関数の実装でこれらのアダプタを使用しています。 v1.8.0のリリースに関する詳細は、以下の公式ブログ記事をご参照ください。 https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web Pooya Parsa氏の GitHub リポジトリ でいくつかのランタイムプラットフォームを対象としたサンプルが公開されています。興味があれば参照してみてください。 https://github.com/pi0/h3-on-edge 一方、今回実装した認証・認可プロキシの挙動としては、 認証・認可プロキシから直接レスポンスを返したいケース(未認証ユーザーのログイン画面へのリダイレクトなど) 後続の CDN にコンテンツ取得処理を委譲したいケース(コンテンツ閲覧が許可されるリク エス トなど) という2パターンが存在しており、特にLambda@EdgeやNetlify Edge Functionsでは、Webフレームワークの挙動(常にレスポンスを返す挙動)とマッチしていない点もあります。それらのサービスでは、ハンドラー関数の戻り値として、①のケースの場合はレスポンスオブジェクトを、②のケースの場合はリク エス トオブジェクトをリターンすることが期待されており、常にレスポンスオブジェクトをリターンする訳ではないためです。 Webフレームワーク自体を利用しないことも検討しましたが、検証の末、以下の理由から最終的にh3の採用を決めました。 h3には、上述のような挙動に有用な onAfterResponse 、 onError などのグローバルフックが存在すること h3の導入による処理のオーバーヘッドが少なく十分高速であったこと コアロジック実装の共 通化 ・簡易化ができると考えたこと(コアロジックでは、リク エス トやレスポンスを取り扱うイベントオブジェクトとしてh3の API のみを使用しており、ランタイム差異を意識しないで済む) 利用者側でのカスタムロジック実装時にh3が提供する便利なユーティリティを利用可能とすることで利便性向上に繋がると考えたこと h3が提供するユーティリティの一覧は以下のページで説明されています。 https://www.jsdocs.io/package/h3#package-functions その他の主要ライブラリ その他の主要な依存ライブラリを以下に列挙します。 ランタイム nanoid(IDジェネレータ) unjs/destr(高速な JSON パース) unjs/ufo(URL操作ユーティリティ) 開発用途 TypeScript(静的型付けを加えた JavaScript のスーパーセット) eslint(静的解析) vitest( 単体テスト ) msw(テスト時の API モック) unbuild(ビルドツール) 終わりに 本記事では、iPLAssの新機能として CDN エッジで動作するポータブルな認証・認可プロキシを実装した話について紹介しました。昨今盛り上がりを見せている CDN エッジですが、一つの ユースケース ・実例としてどなたかの参考になれば幸いです。 現在、iPLAssでは積極的な機能強化・製品展開に取り組んでおり、同じチームで働いてくれる仲間を大募集しています。ご興味を持っていただいた方は是非採用ページをご覧ください。 私たちは一緒に働いてくれる仲間を募集しています! 製品・プラットフォーム開発エンジニア 執筆: @ishida_yuma 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
はじめに こんにちは。コミュニケーションIT事業部の石田です。 普段はソフトウェアエンジニアとして iPLAss というローコード開発プラットフォームの開発を担当しています。本記事は 電通国際情報サービス Advent Calendar 2023 の12/14の投稿です。 iPLAssとは、コーディング量を最小限に抑えた迅速なアプリケーション開発を実現するプラットフォームです。ブラウザ操作によるノンプログラミングでのデータ定義やその他各種定義(認証ポリシーや画面表示設定など)を行うことでアプリケーションを構築していくことが可能です。 また、データ管理画面の自動生成、認証/認可、ID連携、帳票出力、通知、カスタムロジック組込、WebAPI開発といったアプリケーションの開発に必要となる機能をデフォルトで提供しています。 iPLAssの機能の一つに、Web上に公開されるコンシューマ向けサイトを会員制サイト化し、iPLAss上で会員管理を行うことを可能とするWAM機能があります。本記事では、WAM機能のエンハンスとして、 CDN エッジ上の JavaScript ランタイムで動作するポータブル(=Node.jsやWorkersなどあらゆる JavaScript ランタイムで動作する)な認証・認可プロキシを実装した話を紹介します。 WAM機能とは まずは、冒頭で述べたiPLAssのWAM機能について簡単に説明します。 WAMは Web Account Manager の略であり、その名の通り、Web上に公開される任意のコンシューマ向けサイトを会員制サイト化し、iPLAss上で会員管理を行うことを可能とする機能です。 ※ 記事公開時点において、WAM機能は エンタープライズ 版限定で提供している機能になります。 WAM機能を使用することで、コンシューマ向けサイト側には手を加えずとも、会員制サイト化が可能になります。また、既存の会員制サイトについて、会員管理機能のみをiPLAssに置き換えて、その他は既存サイト(コンテンツ)を再利用するといったことも可能です。 WAM機能はあくまでiPLAssの機能の一つであり、iPLAssが具備するその他の豊富な機能と組み合わせることにより、あらゆるニーズ・ ユースケース に対応できます。例えば、ヘッドレス CMS のようなコンテンツ管理システムが一般的に具備している機能と同等なデータ管理画面(コンテンツ管理)、データの有効期間ベースのバージョン管理(コンテンツのバージョン管理・予約投稿)、ワークフロー(承認ワークフロー)、監査ログ記録、WebAPI開発といった機能も提供しており、iPLAss上でコンテンツ管理も併せて行ってしまうといったことも可能です。 WAM機能は、以下2つのモジュールから構成されます。 WAMモジュール iPLAssの本体サーバー( Java / Java EE )上で動作し、会員制サイトの会員管理を行うモジュールです。会員管理機能として、認証機能(ログイン・ログアウト)、サイトコンテンツの閲覧権限チェック機能、パスワード リカバリ 、ユーザー情報のセルフメンテナンス機能などを提供しています。 WAM プラグイン モジュール HTTPサーバー上でプロキシとして動作し、サイトコンテンツへのアクセスをフックしてコンテンツ閲覧制御を行うモジュールです。WAMモジュールとHTTP連携し、閲覧権限に応じたアクセス制御、未ログインユーザーのログイン画面へのリダイレクト、認証成功時のアクセス トーク ン受け渡しなどを行います。 これまでは Apache HTTP Server 、 IIS 、 JavaEE 上で動作するWAM プラグイン モジュールを提供してきましたが、今回新たに CDN エッジ上の JavaScript ランタイムで動作する前提で実装されたServerless( JavaScript )版の提供を開始しました。 CDN の前段でエッジプロキシとして動作させて認証・認可チェックを行うことで、 CDN によるコンテンツキャッシュを最大限活用することが可能になっています。 ここからは、新たに開発したServerless( JavaScript )版の全体構成や実装概要について説明します。 全体構成図 以下が、全体構成のイメージ図になります。 技術構成 実装概要 認証・認可プロキシのコアロジックはライブラリ化し、プライベートnpm リポジトリ からnpmパッケージとして利用者に配布しています。併せて、各 CDN エッジランタイムとのアダプターとなるnpmプロジェクトのひな形をいくつか提供するという形をとっています。「ポータブル」を意識し、コアロジックの実装では、特定のランタイムに依存しない API のみを使用しています(Fetch API など)。 ひな形プロジェクトでは、配布するnpmパッケージ内の API (コアロジック)を利用する形で、各 CDN エッジランタイムのハンドラー関数をデフォルトで実装しています。ハンドラー関数の他にも、認証・認可プロキシのアプリケーション設定およびカスタマイズのベースをあらかじめ実装しており、利用者が プログラマブル に設定を行ったり、カスタムロジックを追加したりできるようにしています。 例えば、アプリケーション設定については、ハードコードする以外の選択肢として、 環境変数 を埋め込んだり、Lambda@Edgeの場合には AWS AppConfigやParameter Storeといったサービスを利用するなどの選択肢を取ることが可能です。カスタマイズについては、コアロジック自体をカスタマイズすることや、フックして処理を追加することが可能であり、オリジナルのHTTPリク エス トを改変する(リク エス トヘッダーの追加、クエリパラメータの追加など)といったカスタマイズが可能です。 また、各 CDN エッジランタイムのビルド・デプロイ用のnpm スクリプト などもデフォルトで実装しており、利用者がアプリケーション設定とカスタマイズ実装にフォーカスできるようにしています。 プライベートnpm リポジトリ プライベートなnpm リポジトリ には Sonatype Nexus Repository の OSS 版を利用しています。構築した Nexus Repositoryはプライベートな Maven リポジトリ としての役割も兼ねており、iPLAss エンタープライズ 版の Java モジュールの配布にも利用しています。 Nexus Repositoryは OSS 版で Maven リポジトリ とnpm リポジトリ の両方に対応しており、とても重宝しています。 CDN エッジ(ランタイム) 現在、npmプロジェクトのひな形を配布している CDN エッジランタイムサービスは以下の3つです。 Cloudflare Workers Lambda@Edge Netlify Edge Functions それぞれのサービスの詳細説明はここでは割愛しますが、Workers、Node.js、Denoとあらゆる JavaScript ランタイムで実際に動作することが確認できています。今後も CDN エッジランタイムのトレンドを追いながら対応環境を拡大していきたいと考えています。 Webフレームワーク Webフレームワークとして、 unjs/h3 を採用しています。 h3は、 Nuxt 3 のサーバーエンジンの基となっているWebフレームワークです。 UnJS と呼ばれる JavaScript ライブラリ、ツール、ユーティリティ群を開発するプロジェクト(Nuxt開発チームを主導していたPooya Parsa氏が開発リーダーを務める)によって提供されているライブラリの一つです。 ミニマルかつポータブルを特徴とした高速なWebフレームワークであり、「 CDN エッジ上のあらゆる JavaScript ランタイムで動作させる」という今回の構想にマッチすると判断しました。また、Nuxt 3で利用されているライブラリであるということもあり、信頼性が高いという点も採用理由として挙げられます。個人的にVue.js/Nuxt.jsが好きで、Nuxt 3のキャッチアップをしているときにUnJSに出会いました(Vue.js/Nuxt.js好きというバイアスがかかっていることも否定できません)。 h3では、v1.8.0リリース時に Towards the Edge of the Web! を掲げており、Node.js環境でHTTPサーバーとして動作させる以外に、直接h3を使用するための2つのアダプタ(fetch互換の シグネチャ を持つWebAdapter、それ以外のプレーンな入出力オブジェクトを使用するPlainAdapter)が提供されました。これにより、Cloudflare WorkersやLambda@Edgeといった CDN エッジランタイムでh3を直接使用することが容易になりました(従来は、 unjs/nitro を使用するか、 unjs/unenv と組み合わせたカスタムコードを実装するしかありませんでした)。今回実装した認証・認可プロキシにおいても、各 CDN エッジランタイムのハンドラー関数の実装でこれらのアダプタを使用しています。 v1.8.0のリリースに関する詳細は、以下の公式ブログ記事をご参照ください。 https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web Pooya Parsa氏の GitHub リポジトリ でいくつかのランタイムプラットフォームを対象としたサンプルが公開されています。興味があれば参照してみてください。 https://github.com/pi0/h3-on-edge 一方、今回実装した認証・認可プロキシの挙動としては、 認証・認可プロキシから直接レスポンスを返したいケース(未認証ユーザーのログイン画面へのリダイレクトなど) 後続の CDN にコンテンツ取得処理を委譲したいケース(コンテンツ閲覧が許可されるリク エス トなど) という2パターンが存在しており、特にLambda@EdgeやNetlify Edge Functionsでは、Webフレームワークの挙動(常にレスポンスを返す挙動)とマッチしていない点もあります。それらのサービスでは、ハンドラー関数の戻り値として、①のケースの場合はレスポンスオブジェクトを、②のケースの場合はリク エス トオブジェクトをリターンすることが期待されており、常にレスポンスオブジェクトをリターンする訳ではないためです。 Webフレームワーク自体を利用しないことも検討しましたが、検証の末、以下の理由から最終的にh3の採用を決めました。 h3には、上述のような挙動に有用な onAfterResponse 、 onError などのグローバルフックが存在すること h3の導入による処理のオーバーヘッドが少なく十分高速であったこと コアロジック実装の共 通化 ・簡易化ができると考えたこと(コアロジックでは、リク エス トやレスポンスを取り扱うイベントオブジェクトとしてh3の API のみを使用しており、ランタイム差異を意識しないで済む) 利用者側でのカスタムロジック実装時にh3が提供する便利なユーティリティを利用可能とすることで利便性向上に繋がると考えたこと h3が提供するユーティリティの一覧は以下のページで説明されています。 https://www.jsdocs.io/package/h3#package-functions その他の主要ライブラリ その他の主要な依存ライブラリを以下に列挙します。 ランタイム nanoid(IDジェネレータ) unjs/destr(高速な JSON パース) unjs/ufo(URL操作ユーティリティ) 開発用途 TypeScript(静的型付けを加えた JavaScript のスーパーセット) eslint(静的解析) vitest( 単体テスト ) msw(テスト時の API モック) unbuild(ビルドツール) 終わりに 本記事では、iPLAssの新機能として CDN エッジで動作するポータブルな認証・認可プロキシを実装した話について紹介しました。昨今盛り上がりを見せている CDN エッジですが、一つの ユースケース ・実例としてどなたかの参考になれば幸いです。 現在、iPLAssでは積極的な機能強化・製品展開に取り組んでおり、同じチームで働いてくれる仲間を大募集しています。ご興味を持っていただいた方は是非採用ページをご覧ください。 私たちは一緒に働いてくれる仲間を募集しています! 製品・プラットフォーム開発エンジニア 執筆: @ishida_yuma 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
こんにちは、コミュニケーションIT事業部の瀧川です。 本記事は 電通国際情報サービス Advent Calendar 2023 の12/13(水)の投稿です。 同期の石田と チームきなこ としてISUCON13に参加しました。(初参加) https://isucon.net/archives/58001272.html 競技終盤でアプリケーションを壊してしまい、 記録なし という悔しい結果に終わってしまいました。(T_T) きなこさん(石田の愛犬)、名前を借りておきながらかたじけない。。 ISUCONとは 事前準備 当日の流れ 9:00-10:00 10:00-12:00 12:00-17:00 17:00-18:00 感想 ISUCONとは ISUCONはWebアプリのパフォーマンスチューニングのコンテストです。 年によって差異があるかもしれませんが、おおよそこんな感じかと思います。 当日チューニング対象のサーバーが複数台提供される。 アプリは複数言語で実装が用意されているため選択可能。(今年はGo, Perl , PHP , Python , Ruby , Rust, Node.js) 当日提供される、性能計測ツール(ベンチマーカー)を実行し、性能値 (スコア)を採点する。 競技時間中に記録された最後のスコアで順位を競う。 今年のお題は ISUPipe という動画配信サービスでした。 https://www.youtube.com/watch?v=OOyInZbM85k 事前準備 休日に何度か集まって準備しました。 過去問は数年分解きました。 初期セットアップ、デプロイ、ログ集計についてはなるべく自動化できるよう シェルスクリプト を用意しました。 https://github.com/Akihiro1001/isucon 集計シェルを実行すると GitHub issueにNginxの アクセスログ と MySQL のスロークエリの集計結果が出力されます。 また、ChatGPTと GitHub Copilotに課金して、前日は早く寝ました。💤 当日の流れ 9:00-10:00 平日よりも早起きをして、恵比寿駅に待ち合わせ。 コンビニでランチを買い、予約しておいた レンタルオフィス に無事到着。 作業環境を整え、眠気覚ましのコーヒーを大量に用意。 10:00-12:00 競技が開始し、まずは予定通りの分担で作業しました。 瀧川 事前に用意した各種 シェルスクリプト の微調整と各サーバーへの配置 アプリケーションのソースや ミドルウェア の設定ファイルを GitHub 管理 各種集計ツール(pt-query-digest, alp)のインストール Nginx, MySQL のログ設定やパラメータ調整 デプロイ → 計測 → 集計 の一連の処理を流す 石田 マニュアルの読み込みとアプリケーションの仕様把握 ソースコード の読み込みと ボトルネック になりそうな箇所の洗い出し 構成としては、以下がオールインワンになったサーバーが3台配られました。 DNS : PowerDNS + MySQL アプリ : Nginx + Go + MySQL Nginxと MySQL はヤマが的中して助かりました。 Apache と PostgreSQL だったら 心が折れる ところでした。 特徴的だったのは、 DNS もチューニング対象だったことです。 12:00-17:00 ボトルネック が見えてきたのでチューニングに着手。 以下の改善を入れました。 インデックスの追加 N+1の改善 キャッシュできそうな箇所はオンメモリにキャッシュ インサートをループしている箇所をバルクインサートに変更 PREPARED STATEMENTを無効化 アプリケーションのDBを別サーバーに移行 20位くらいに躍り出る瞬間もあり、盛り上がりました。😆 17:00-18:00 改善が進むと、 DNS への水責め攻撃が激しくなり、 ボトルネック がアプリから DNS へと移りました。 DNS レコードの TTL やcache- ttl が0に設定されていたため、設定を変更しキャッシュを有効化しました。 ですが、水責め攻撃にはそれほど効果が見られず。。 とりあえず DNS を別サーバーに移行して負荷分散しようとしたあたりで DNS 機能がクラッシュ😱 最後まで DNS を復旧できず、 記録なし に終わってしまいました。。 感想 来年に向けて反省点を残しておきます。 変更点は必ず残しておき、いつでも戻せるようにしておくべきでした。サーバーを直接いじる時は、とりあえず git init しておくのが良さそうです。 今回利用したGo言語は、普段業務で触っていないため、時間を作って慣れておくべきでした。 集計シェルはベンチマーカーが完了するのを待ってから手動で実行していました。今考えると、/initializeのエンドポイントでキックして、一定時間スリープ後に自動で集計するようにすれば手間を減らせました。 悔しい結果となってしまいましたが、とても良い経験になりました。 爆速なWebアプリを開発できるよう今後も精進します💪 一緒に参加した石田が明日の アドベントカレンダー を担当します。 執筆: @takigawa.akihiro 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
こんにちは、コミュニケーションIT事業部の瀧川です。 本記事は 電通国際情報サービス Advent Calendar 2023 の12/13(水)の投稿です。 同期の石田と チームきなこ としてISUCON13に参加しました。(初参加) https://isucon.net/archives/58001272.html 競技終盤でアプリケーションを壊してしまい、 記録なし という悔しい結果に終わってしまいました。(T_T) きなこさん(石田の愛犬)、名前を借りておきながらかたじけない。。 ISUCONとは 事前準備 当日の流れ 9:00-10:00 10:00-12:00 12:00-17:00 17:00-18:00 感想 ISUCONとは ISUCONはWebアプリのパフォーマンスチューニングのコンテストです。 年によって差異があるかもしれませんが、おおよそこんな感じかと思います。 当日チューニング対象のサーバーが複数台提供される。 アプリは複数言語で実装が用意されているため選択可能。(今年はGo, Perl , PHP , Python , Ruby , Rust, Node.js) 当日提供される、性能計測ツール(ベンチマーカー)を実行し、性能値 (スコア)を採点する。 競技時間中に記録された最後のスコアで順位を競う。 今年のお題は ISUPipe という動画配信サービスでした。 https://www.youtube.com/watch?v=OOyInZbM85k 事前準備 休日に何度か集まって準備しました。 過去問は数年分解きました。 初期セットアップ、デプロイ、ログ集計についてはなるべく自動化できるよう シェルスクリプト を用意しました。 https://github.com/Akihiro1001/isucon 集計シェルを実行すると GitHub issueにNginxの アクセスログ と MySQL のスロークエリの集計結果が出力されます。 また、ChatGPTと GitHub Copilotに課金して、前日は早く寝ました。💤 当日の流れ 9:00-10:00 平日よりも早起きをして、恵比寿駅に待ち合わせ。 コンビニでランチを買い、予約しておいた レンタルオフィス に無事到着。 作業環境を整え、眠気覚ましのコーヒーを大量に用意。 10:00-12:00 競技が開始し、まずは予定通りの分担で作業しました。 瀧川 事前に用意した各種 シェルスクリプト の微調整と各サーバーへの配置 アプリケーションのソースや ミドルウェア の設定ファイルを GitHub 管理 各種集計ツール(pt-query-digest, alp)のインストール Nginx, MySQL のログ設定やパラメータ調整 デプロイ → 計測 → 集計 の一連の処理を流す 石田 マニュアルの読み込みとアプリケーションの仕様把握 ソースコード の読み込みと ボトルネック になりそうな箇所の洗い出し 構成としては、以下がオールインワンになったサーバーが3台配られました。 DNS : PowerDNS + MySQL アプリ : Nginx + Go + MySQL Nginxと MySQL はヤマが的中して助かりました。 Apache と PostgreSQL だったら 心が折れる ところでした。 特徴的だったのは、 DNS もチューニング対象だったことです。 12:00-17:00 ボトルネック が見えてきたのでチューニングに着手。 以下の改善を入れました。 インデックスの追加 N+1の改善 キャッシュできそうな箇所はオンメモリにキャッシュ インサートをループしている箇所をバルクインサートに変更 PREPARED STATEMENTを無効化 アプリケーションのDBを別サーバーに移行 20位くらいに躍り出る瞬間もあり、盛り上がりました。😆 17:00-18:00 改善が進むと、 DNS への水責め攻撃が激しくなり、 ボトルネック がアプリから DNS へと移りました。 DNS レコードの TTL やcache- ttl が0に設定されていたため、設定を変更しキャッシュを有効化しました。 ですが、水責め攻撃にはそれほど効果が見られず。。 とりあえず DNS を別サーバーに移行して負荷分散しようとしたあたりで DNS 機能がクラッシュ😱 最後まで DNS を復旧できず、 記録なし に終わってしまいました。。 感想 来年に向けて反省点を残しておきます。 変更点は必ず残しておき、いつでも戻せるようにしておくべきでした。サーバーを直接いじる時は、とりあえず git init しておくのが良さそうです。 今回利用したGo言語は、普段業務で触っていないため、時間を作って慣れておくべきでした。 集計シェルはベンチマーカーが完了するのを待ってから手動で実行していました。今考えると、/initializeのエンドポイントでキックして、一定時間スリープ後に自動で集計するようにすれば手間を減らせました。 悔しい結果となってしまいましたが、とても良い経験になりました。 爆速なWebアプリを開発できるよう今後も精進します💪 一緒に参加した石田が明日の アドベントカレンダー を担当します。 執筆: @takigawa.akihiro 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部ソフトウェアデザインセンターの徳山です。 本記事は 電通国際情報サービス Advent Calendar 2023 の12月12日の記事となります。 ちなみに12月12日は「いい字一字」の語呂合わせで「 漢字の日 」だそうです。へぇ〜! 本記事はタイトルにありますFlutterFlowというノーコードツールを使用した記事となります。ノーコード・ローコードツールの業務利用に対してまだまだ懐疑的な開発者の方も多いと思いますが、FlutterFlowを実際に利用することで見えてきたことを本記事を通してご紹介できればと思います。 また、FlutterFlowに関連した記事を新たに追加しましたのでFlutterFlowやFirebaseについてより深く知りたい方はぜひ覗いてみてください。 FlutterFlowとは?ノーコードでスマホアプリ開発を始める方法 - 電通総研 テックブログ FlutterFlow vs Adalo:ノーコードモバイルアプリ開発ツールの比較 - 電通総研 テックブログ FlutterFlowに制約はある?できることとできないこと - 電通総研 テックブログ FlutterFlowでFirebaseと連携する方法 - 電通総研 テックブログ FlutterFlowでサクっと開発する社内業務スマホアプリ - 電通総研 テックブログ この記事を読むと何がわかるのか FlutterFlowとは何か 今回は何を行うのか 実際に作ってみる 前提条件 大まかな時間配分 成果物 作成の過程 初期設定 認証機能を実装 記録関連のページと機能を実装 カスタムファンクションの実装例 クエリーのフィルター設定例 マイページ関連のページと機能を実装 その他UI調整 評価 良い点 辛い点 まとめ この記事を読むと何がわかるのか FlutterFlowとは何か FlutterFlowの良い点、イマイチな点 FlutterFlowの使いどころ FlutterFlowとは何か FlutterFlowとは、 クロスプラットフォーム 開発に特化したノーコードツールとなります。FlutterFlowだけで iOS 、 Android 、Webでの開発に一度に対応できるというものです。多くのUI コンポーネント (FlutterFlowでは ウィジェット )が用意されており、ホバーなどのアクションやバリデーション、特定の条件下での表示・非表示の制御など全て GUI で設定を行うことができます。 また、有料機能となりますがコードのエクスポート機能を備えているため、途中からコードベースの開発に切り替えることも可能です。 今回は何を行うのか FlutterFlowの利用可能性についてご紹介するため簡単なデモアプリを作成してみたいと思います。 ただし、普通に作っても面白くないため今回はあえて5時間という時間制限を設けます。 ノーコード・ローコードツールを使うケースを想定するとあまり時間をかけずにサクッとアプリを作りたい場合が多いと思いますので、実例を示す点において良い制限ではないでしょうか。 実際に作ってみる 今回は「日々の筋トレを記録するアプリ」を作っていきます。 本アプリは下記の機能を持つアプリとなっています。 ト レーニン グ内容の記録と確認を行うことができる 「日付、種目、重量・回数・セット数、コメント」を記録 本日と過去の2種類の内容を確認 認証機能を利用できる アカウント作成、ログイン、ログアウト プロフィールの作成、変更ができる プロフィール画像 、アカウント名、メールアドレス 作る際のスタート地点を明確にするため前提としていくつか条件を設定しておきます。 前提条件 プロジェクトテンプレートは使用しない ページテンプレートやAI機能、Flows機能* の利用は可能 バックエンドやデータベースは連携可能なFirebaseサービスを利用 Flows:複数のページや コンポーネント にまたがる機能セットのテンプレート 大まかな時間配分 初期設定(Firebaseとの連携):30分 大枠のUI作成:2時間半 機能実装:2時間半 成果物 実際に作ったアプリのストーリーボードは下記となります。 時間の制約上かなり簡素なアプリですが、最低限のログイン、ログアウト機能や CRUD 機能を実装することができました。 作成の過程 では、どのようにアプリを作成したのか順を追って説明します。 初期設定 まずはアプリケーションの初期設定を行います。 新規作成時にアプリケーションの配色やFirebaseとのセットアップを行うことができます。これらはもちろん後から変更することもできます。 次のステップでは実際にFlutterFlowからFirebaseでのプロジェクトが作成できます。 どのように設定や連携すれば良いか不安、という場合でもプロジェクト作成後の設定画面ではドキュメントや チュートリアル 動画へのリンクが用意されています。英語ですが、どの設定のページにも用意されているので随時参照しながらアプリの作成を進めることができます。 ドキュメントや チュートリアル に従い下記の3つのセットアップを終わらせます。 認証:Firebase Authentication データベース:Firestore Database ストレージ:Firebase Storage 認証機能を実装 まずはUIを実装します。便利なことにFlutterFlowでは Flows と呼ばれる機能が最近実装されました。 これはよく使われる一連の機能とページをまとめてプロジェクトで利用できるものです。 例えばアカウント作成、ログイン、パスワードリセットの機能とページを丸ごとテンプレートとして利用できます。 個別で作成するとUI作成とページの遷移の連携、フォーカス時のアクションなど時間がかかる作業もあっという間に実装できてしまいます。 今回はアカウント作成周りの機能とページで利用します。 FlutterFlowは GUI ツールのため、用意された豊富な ウィジェット (UI コンポーネント のこと)を ドラッグ&ドロップ で配置できます。ページの構成は Figma ようなツリー構造で表示されます。 構造上配置できない箇所へのドロップやプロパティの変更時には変更は破棄されエラーが表示されるようになっています。 それぞれの ウィジェット には アクション を設定できます。 例えば、「プロフィールを変更する」ボタンを押したら下記のアクションを個別に組み合わせて設定できます。 ①フォームのバリデーションを実施 ②パスしたらFirebaseで用意したusersコレクションにデータを保存 ③指定したページに遷移 ④スナックバーを表示 認証のFlowsにはアクションも自動的に設定されていたため、アクションに関してはログイン後のページだけ指定を行うだけで済みました。 記録関連のページと機能を実装 できるだけページ数を減らすために記録関連の新規作成、編集、削除はモーダルで行うようにします。 ワークアウト用のコレクションも必要になりますが、FlutterFlow上で簡単に登録できます。 アクション同様、コレクションへのクエリ操作もページの ウィジェット 選択時に選択できます。 フォームバリデーションやエラーメッセージの表示もform ウィジェット からフィールドごとに簡単に行うことができます。 また、記録の一覧取得もworkoutコレクションへのクエリーを設定することで実装します。 FlutterFlowには特定の日付を指定して該当するデータだけを取得する機能、例えば12月12日に記録したト レーニン グのデータだけを抜き出すといった機能は提供されていません。その代わり、指定した値との大小を比較してフィルターする条件をクエリーに付与できます。そのため、記録日の開始時刻(0時0分0秒)と翌日の開始時刻を取得する関数を独自に作成し、記録日時の値と比較することで一覧を取得するようにします。 FlutterFlowで提供されていない独自の関数を利用する場合、 カスタムファンクション という機能を利用できます。 Dart 言語に関する知識が必要になり実装経験はありませんでしたが、ChatGPTを利用して実装を行いました。 以下は「本日と次の日の開始時刻」を取得してDateTime型で返却する独自関数の実装例とクエリーの設定例です。 カスタムファンクションの実装例 DateTime? getTodayStartTime() { /// MODIFY CODE ONLY BELOW THIS LINE DateTime now = DateTime.now(); DateTime todayStartTime = DateTime(now.year, now.month, now.day); return todayStartTime; /// MODIFY CODE ONLY ABOVE THIS LINE } DateTime? getTomorrowStartTime() { /// MODIFY CODE ONLY BELOW THIS LINE DateTime now = DateTime.now(); DateTime tomorrowStartTime = DateTime(now.year, now.month, now.day + 1); return tomorrowStartTime; /// MODIFY CODE ONLY ABOVE THIS LINE } クエリーのフィルター設定例 filter条件を3つ指定しています。 記録日時が記録日翌日の0時0分0秒より前 記録日時が記録日当日の0時0分0秒より後 ログインユーザー本人が作成した記録 以上のようにフィルター条件を組み合わせることで当日と昨日以前の記録一覧をそれぞれ取得することができるようになりました。 マイページ関連のページと機能を実装 行うことは記録関連と同じため特に真新しいことはありません。 ログアウト時にダイアログを表示し、押されたボタンに応じたアクションを設定しています。 その他UI調整 ページ下部と上部に表示するナビゲーションバーとアプリケーションバーの設定を行います。 どちらも画面上から簡単に設定できます。 評価 実際にアプリケーションを作ってみることで、FlutterFlowの良し悪しが見えてきましたのでまとめてました。 良い点 アップデートサイクルが早い 毎月のように リリースノート が更新されており、新規機能のアップデートや改善が行われているためプロダクトしての力の入れ具合を感じます。 見やすく操作性の良いUI ノーコード関連のツールは多くありますが、UIとして圧迫感を感じず操作性も良いため設定項目の多いツールとして使いやすいと感じました。 FlutterFlowにはAI機能も組み込まれており、テンプレート機能と組み合わせることで部分的にですが高速に モックアップ を作成することができました。 ドキュメントが豊富・親切 英語のみですが、ドキュメント量が豊富で動画付きの箇所も多いためため操作で困ってしまった箇所はほとんどありませんでした。 コードベースの開発に切り替えられる柔軟性 有料機能ですが、FlutterFlowで開発したアプリケーションはFlutterとしてexportすることができ、 Dart での開発に切り替えることができます。そのため、初期段階はFlutterFlowで開発し、手の込んだ開発や開発人数が増えてきた段階でコードベースの開発に切り替えるといったことができます。 コードエクスポートに対応したノーコードツールはあまりないため、開発体制を柔軟に切り替えられ点は良いと感じました。 辛い点 開発体験として惜しい点が多い 確認時のテストモードの立ち上げやリロードに時間がかかります。立ち上げは数分程度、変更を反映する際のリロードには10秒程度かかります。 また、セッションも10分程度しか維持されないため作業に集中していると頻繁に立ち上げ直しすることになりました。 GUI ツールの性質上仕方がないですがショートカットが少ないためマウスでの操作が中心となります。普段 VS Code でショートカットを多用する身としては最初のうちはフラストレーションが溜まりました。 共同編集や複雑な操作には向かない プランをアップグレードすることで共同作業ができます。ただし、同じファイルを同時にといった スプレッドシート のようなことはできないため複数人での共同編集には向かない印象です。 また、UIやアニメーションといった見た目に関する機能は柔軟かつ豊富ですが、バックエンドと連携する機能面では少し物足りない印象です。例えばフォーム送信時に複数のコレクションにデータを作成するといったアクション設定ができない、日時を絞り込んだデータを取得できないといったことが今回のアプリ作成時にわかりました。 利用シーンによっては Dart やFlutterFlowのキャッチアップハードルが高い FlutterFlowはできることが多い一方で、何ができるのかを把握することが大変です。英語ということもあり機能の解釈が間違っていたり該当するドキュメントをうまく見つけることができないといったことがよくありました。 また、中規模以上の システム開発 での利用となるとカスタムファンクションの利用やコードベースの開発が必要になることが予想されます。そうした場合に Dart などのモバイルアプリ向け言語の利用経験がないとキャッチアップに時間を要するためハードルとしては高くなってしまうのではないかと感じました。 まとめ ノーコードツールの利用経験がなかったこともあり使い勝手がわからず利用シーンも今までイメージがつきませんでした。今回実際に使ってみることで、モバイルアプリケーションにおけるMVP(Minimum Viable Product)や モックアップ の作成といった用途において利用できるのではないかと感じました。 システム開発 において中心的存在となるものではないかもしれませんが、既存の開発手法と合わせて価値を発揮できる可能性はあると思いますので皆さんもお時間があればぜひ触ってみてください。 私たちは一緒に働いてくれる仲間を募集しています! フルサイクルエンジニア 執筆: @tokuyama 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部ソフトウェアデザインセンターの徳山です。 本記事は 電通国際情報サービス Advent Calendar 2023 の12月12日の記事となります。 ちなみに12月12日は「いい字一字」の語呂合わせで「 漢字の日 」だそうです。へぇ〜! 本記事はタイトルにありますFlutterFlowというノーコードツールを使用した記事となります。ノーコード・ローコードツールの業務利用に対してまだまだ懐疑的な開発者の方も多いと思いますが、FlutterFlowを実際に利用することで見えてきたことを本記事を通してご紹介できればと思います。 この記事を読むと何がわかるのか FlutterFlowとは何か 今回は何を行うのか 実際に作ってみる 前提条件 大まかな時間配分 成果物 作成の過程 初期設定 認証機能を実装 記録関連のページと機能を実装 カスタムファンクションの実装例 クエリーのフィルター設定例 マイページ関連のページと機能を実装 その他UI調整 評価 良い点 辛い点 まとめ この記事を読むと何がわかるのか FlutterFlowとは何か FlutterFlowの良い点、イマイチな点 FlutterFlowの使いどころ FlutterFlowとは何か FlutterFlowとは、 クロスプラットフォーム 開発に特化したノーコードツールとなります。FlutterFlowだけで iOS 、 Android 、Webでの開発に一度に対応できるというものです。多くのUI コンポーネント (FlutterFlowでは ウィジェット )が用意されており、ホバーなどのアクションやバリデーション、特定の条件下での表示・非表示の制御など全て GUI で設定を行うことができます。 また、有料機能となりますがコードのエクスポート機能を備えているため、途中からコードベースの開発に切り替えることも可能です。 今回は何を行うのか FlutterFlowの利用可能性についてご紹介するため簡単なデモアプリを作成してみたいと思います。 ただし、普通に作っても面白くないため今回はあえて5時間という時間制限を設けます。 ノーコード・ローコードツールを使うケースを想定するとあまり時間をかけずにサクッとアプリを作りたい場合が多いと思いますので、実例を示す点において良い制限ではないでしょうか。 実際に作ってみる 今回は「日々の筋トレを記録するアプリ」を作っていきます。 本アプリは下記の機能を持つアプリとなっています。 ト レーニン グ内容の記録と確認を行うことができる 「日付、種目、重量・回数・セット数、コメント」を記録 本日と過去の2種類の内容を確認 認証機能を利用できる アカウント作成、ログイン、ログアウト プロフィールの作成、変更ができる プロフィール画像 、アカウント名、メールアドレス 作る際のスタート地点を明確にするため前提としていくつか条件を設定しておきます。 前提条件 プロジェクトテンプレートは使用しない ページテンプレートやAI機能、Flows機能* の利用は可能 バックエンドやデータベースは連携可能なFirebaseサービスを利用 Flows:複数のページや コンポーネント にまたがる機能セットのテンプレート 大まかな時間配分 初期設定(Firebaseとの連携):30分 大枠のUI作成:2時間半 機能実装:2時間半 成果物 実際に作ったアプリのストーリーボードは下記となります。 時間の制約上かなり簡素なアプリですが、最低限のログイン、ログアウト機能や CRUD 機能を実装することができました。 作成の過程 では、どのようにアプリを作成したのか順を追って説明します。 初期設定 まずはアプリケーションの初期設定を行います。 新規作成時にアプリケーションの配色やFirebaseとのセットアップを行うことができます。これらはもちろん後から変更することもできます。 次のステップでは実際にFlutterFlowからFirebaseでのプロジェクトが作成できます。 どのように設定や連携すれば良いか不安、という場合でもプロジェクト作成後の設定画面ではドキュメントや チュートリアル 動画へのリンクが用意されています。英語ですが、どの設定のページにも用意されているので随時参照しながらアプリの作成を進めることができます。 ドキュメントや チュートリアル に従い下記の3つのセットアップを終わらせます。 認証:Firebase Authentication データベース:Firestore Database ストレージ:Firebase Storage 認証機能を実装 まずはUIを実装します。便利なことにFlutterFlowでは Flows と呼ばれる機能が最近実装されました。 これはよく使われる一連の機能とページをまとめてプロジェクトで利用できるものです。 例えばアカウント作成、ログイン、パスワードリセットの機能とページを丸ごとテンプレートとして利用できます。 個別で作成するとUI作成とページの遷移の連携、フォーカス時のアクションなど時間がかかる作業もあっという間に実装できてしまいます。 今回はアカウント作成周りの機能とページで利用します。 FlutterFlowは GUI ツールのため、用意された豊富な ウィジェット (UI コンポーネント のこと)を ドラッグ&ドロップ で配置できます。ページの構成は Figma ようなツリー構造で表示されます。 構造上配置できない箇所へのドロップやプロパティの変更時には変更は破棄されエラーが表示されるようになっています。 それぞれの ウィジェット には アクション を設定できます。 例えば、「プロフィールを変更する」ボタンを押したら下記のアクションを個別に組み合わせて設定できます。 ①フォームのバリデーションを実施 ②パスしたらFirebaseで用意したusersコレクションにデータを保存 ③指定したページに遷移 ④スナックバーを表示 認証のFlowsにはアクションも自動的に設定されていたため、アクションに関してはログイン後のページだけ指定を行うだけで済みました。 記録関連のページと機能を実装 できるだけページ数を減らすために記録関連の新規作成、編集、削除はモーダルで行うようにします。 ワークアウト用のコレクションも必要になりますが、FlutterFlow上で簡単に登録できます。 アクション同様、コレクションへのクエリ操作もページの ウィジェット 選択時に選択できます。 フォームバリデーションやエラーメッセージの表示もform ウィジェット からフィールドごとに簡単に行うことができます。 また、記録の一覧取得もworkoutコレクションへのクエリーを設定することで実装します。 FlutterFlowには特定の日付を指定して該当するデータだけを取得する機能、例えば12月12日に記録したト レーニン グのデータだけを抜き出すといった機能は提供されていません。その代わり、指定した値との大小を比較してフィルターする条件をクエリーに付与できます。そのため、記録日の開始時刻(0時0分0秒)と翌日の開始時刻を取得する関数を独自に作成し、記録日時の値と比較することで一覧を取得するようにします。 FlutterFlowで提供されていない独自の関数を利用する場合、 カスタムファンクション という機能を利用できます。 Dart 言語に関する知識が必要になり実装経験はありませんでしたが、ChatGPTを利用して実装を行いました。 以下は「本日と次の日の開始時刻」を取得してDateTime型で返却する独自関数の実装例とクエリーの設定例です。 カスタムファンクションの実装例 DateTime ? getTodayStartTime () { /// MODIFY CODE ONLY BELOW THIS LINE DateTime now = DateTime . now (); DateTime todayStartTime = DateTime (now.year, now.month, now.day); return todayStartTime; /// MODIFY CODE ONLY ABOVE THIS LINE } DateTime ? getTomorrowStartTime () { /// MODIFY CODE ONLY BELOW THIS LINE DateTime now = DateTime . now (); DateTime tomorrowStartTime = DateTime (now.year, now.month, now.day + 1 ); return tomorrowStartTime; /// MODIFY CODE ONLY ABOVE THIS LINE } クエリーのフィルター設定例 filter条件を3つ指定しています。 記録日時が記録日翌日の0時0分0秒より前 記録日時が記録日当日の0時0分0秒より後 ログインユーザー本人が作成した記録 以上のようにフィルター条件を組み合わせることで当日と昨日以前の記録一覧をそれぞれ取得することができるようになりました。 マイページ関連のページと機能を実装 行うことは記録関連と同じため特に真新しいことはありません。 ログアウト時にダイアログを表示し、押されたボタンに応じたアクションを設定しています。 その他UI調整 ページ下部と上部に表示するナビゲーションバーとアプリケーションバーの設定を行います。 どちらも画面上から簡単に設定できます。 評価 実際にアプリケーションを作ってみることで、FlutterFlowの良し悪しが見えてきましたのでまとめてました。 良い点 アップデートサイクルが早い 毎月のように リリースノート が更新されており、新規機能のアップデートや改善が行われているためプロダクトしての力の入れ具合を感じます。 見やすく操作性の良いUI ノーコード関連のツールは多くありますが、UIとして圧迫感を感じず操作性も良いため設定項目の多いツールとして使いやすいと感じました。 FlutterFlowにはAI機能も組み込まれており、テンプレート機能と組み合わせることで部分的にですが高速に モックアップ を作成することができました。 ドキュメントが豊富・親切 英語のみですが、ドキュメント量が豊富で動画付きの箇所も多いためため操作で困ってしまった箇所はほとんどありませんでした。 コードベースの開発に切り替えられる柔軟性 有料機能ですが、FlutterFlowで開発したアプリケーションはFlutterとしてexportすることができ、 Dart での開発に切り替えることができます。そのため、初期段階はFlutterFlowで開発し、手の込んだ開発や開発人数が増えてきた段階でコードベースの開発に切り替えるといったことができます。 コードエクスポートに対応したノーコードツールはあまりないため、開発体制を柔軟に切り替えられ点は良いと感じました。 辛い点 開発体験として惜しい点が多い 確認時のテストモードの立ち上げやリロードに時間がかかります。立ち上げは数分程度、変更を反映する際のリロードには10秒程度かかります。 また、セッションも10分程度しか維持されないため作業に集中していると頻繁に立ち上げ直しすることになりました。 GUI ツールの性質上仕方がないですがショートカットが少ないためマウスでの操作が中心となります。普段 VS Code でショートカットを多用する身としては最初のうちはフラストレーションが溜まりました。 共同編集や複雑な操作には向かない プランをアップグレードすることで共同作業ができます。ただし、同じファイルを同時にといった スプレッドシート のようなことはできないため複数人での共同編集には向かない印象です。 また、UIやアニメーションといった見た目に関する機能は柔軟かつ豊富ですが、バックエンドと連携する機能面では少し物足りない印象です。例えばフォーム送信時に複数のコレクションにデータを作成するといったアクション設定ができない、日時を絞り込んだデータを取得できないといったことが今回のアプリ作成時にわかりました。 利用シーンによっては Dart やFlutterFlowのキャッチアップハードルが高い FlutterFlowはできることが多い一方で、何ができるのかを把握することが大変です。英語ということもあり機能の解釈が間違っていたり該当するドキュメントをうまく見つけることができないといったことがよくありました。 また、中規模以上の システム開発 での利用となるとカスタムファンクションの利用やコードベースの開発が必要になることが予想されます。そうした場合に Dart などのモバイルアプリ向け言語の利用経験がないとキャッチアップに時間を要するためハードルとしては高くなってしまうのではないかと感じました。 まとめ ノーコードツールの利用経験がなかったこともあり使い勝手がわからず利用シーンも今までイメージがつきませんでした。今回実際に使ってみることで、モバイルアプリケーションにおけるMVP(Minimum Viable Product)や モックアップ の作成といった用途において利用できるのではないかと感じました。 システム開発 において中心的存在となるものではないかもしれませんが、既存の開発手法と合わせて価値を発揮できる可能性はあると思いますので皆さんもお時間があればぜひ触ってみてください。 私たちは一緒に働いてくれる仲間を募集しています! フルサイクルエンジニア 執筆: @tokuyama 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
こんにちは、金融ソリューション事業部兼テックブログ編集部の若本です。 今回は、テックブログ編集部で取り組んでいる運営改善の取り組みの一部をご紹介します。 本記事は 電通国際情報サービス Advent Calendar 2023 の11日目の記事です。 テックブログ編集部について 運営上の課題 アプローチ レビュアーガチャの仕組み おわりに テックブログ編集部について ISIDにはテックブログ編集部というバーチャル組織があり、ISIDテックブログの企画や運営を行っています 。 テックブログ編集部の中身は 中村さんの記事 に詳しくまとまっていますので、ご興味のある方はこちらもぜひご一読ください。 編集部のうち、私は執筆管理チームに所属しています。執筆管理チームの役割は、一連のレビュープロセスを含めた記事の管理です。図にまとめると以下のようになります。 これらのタスクはチャットベースで執筆管理チーム内の認識を合わせつつ、2週に1度の定例で状況確認や改善のためのディスカッションを行っています。 運営上の課題 テックブログ編集部では、記事のレビューに関して以下のような課題がありました。 レビュアーがなかなかつかず、チャットでの調整が必要になる場合がある 特に実装系、ニッチな技術領域の記事は 心理的 にレビューしづらい状態にありました。 そもそも、記事のレビュー待ちメールに気が付いていない 執筆された記事はレビュー待ちの状態になりメールが飛びますが、業務メールの波に流されてしまいレビューされない記事が発生することがありました。 こういった問題は 長期的にみると執筆者離れを引き起こすため、テックブログ運営においては非常に重要な問題 です。 特にISID テックブログでは、記事の執筆は個々人のモチベーションに委ねるところが大きいため、執筆者のストレス増・モチベーション低下につながるプロセスは改善していく必要があります。 アプローチ 上記の課題に対して、テックブログ編集部では以下の対策を考えました。 レビュアーガチャ 技術の詳細な内容はともかく、 発信して問題ない内容や文章になっているかは編集部員がランダムで担当する のがいいのでは? 気づきにくいメールやレビュー管理画面ではなく、 メンションで通知を飛ばせるTeamsで通知する形にしたい 。 今回は、これらの仕組みをPower Autometeの クラウド フローを使って実現します。具体的には、「テックブログのレビュー依頼メール」が飛んできたときにTeamsのチャネル上でランダムにメンションするフローを作成します。 Power Autometeのフローについては 根本さんの記事 をご覧ください。 レビュアーガチャの仕組み 「テックブログのレビュー依頼メール」は以下のようなものです。ISID テックブログ編集部ではレビュー管理にShodoを採用しており、執筆完了の折にはメールが自動配信されます。 このとき、メール本文には「記事のタイトル/レビューリンク」が、メールの メタデータ には「差出人」が含まれています。 レビューに必要な情報はメールに含まれているため、メールの情報を解析して必要な情報のみ抽出します。 後は、 ガチャ要素として編集部員のメンバーから1人ランダムに割り当てます 。これは、編集部員のメンバーの一覧を取得して乱数で選択することで実現することができます。 仕組みは以下の通りです。 上記をPower Automate上でフローとして実装していきました(フロー全体のスクショは長すぎたため割愛します)。 発行すると、レビュー依頼メール受信時にランダムでメンションされるようになります。これにより、 レビュー担当予定者者が明確になり、レビュー割り当て時にはメンション付きで通知されるようになりました! おわりに 本記事では、テックブログ編集部の施策例としてレビュアーガチャが実現するまでの流れをご紹介しました。 今回は触れませんでしたが、上記のようないかにもテックブログらしい施策だけではなく、執筆マニュアルや相談窓口の設置などといったレビューフロー全体の整備を行っています。ISID テックブログでは、今後も執筆者の方がいきいきと執筆できる環境づくりを目指して取り組みを進めていきます。 執筆: @wakamoto.ryosuke 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは、金融ソリューション事業部兼テックブログ編集部の若本です。 今回は、テックブログ編集部で取り組んでいる運営改善の取り組みの一部をご紹介します。 本記事は 電通国際情報サービス Advent Calendar 2023 の11日目の記事です。 テックブログ編集部について 運営上の課題 アプローチ レビュアーガチャの仕組み おわりに テックブログ編集部について ISIDにはテックブログ編集部というバーチャル組織があり、ISIDテックブログの企画や運営を行っています 。 テックブログ編集部の中身は 中村さんの記事 に詳しくまとまっていますので、ご興味のある方はこちらもぜひご一読ください。 編集部のうち、私は執筆管理チームに所属しています。執筆管理チームの役割は、一連のレビュープロセスを含めた記事の管理です。図にまとめると以下のようになります。 これらのタスクはチャットベースで執筆管理チーム内の認識を合わせつつ、2週に1度の定例で状況確認や改善のためのディスカッションを行っています。 運営上の課題 テックブログ編集部では、記事のレビューに関して以下のような課題がありました。 レビュアーがなかなかつかず、チャットでの調整が必要になる場合がある 特に実装系、ニッチな技術領域の記事は 心理的 にレビューしづらい状態にありました。 そもそも、記事のレビュー待ちメールに気が付いていない 執筆された記事はレビュー待ちの状態になりメールが飛びますが、業務メールの波に流されてしまいレビューされない記事が発生することがありました。 こういった問題は 長期的にみると執筆者離れを引き起こすため、テックブログ運営においては非常に重要な問題 です。 特にISID テックブログでは、記事の執筆は個々人のモチベーションに委ねるところが大きいため、執筆者のストレス増・モチベーション低下につながるプロセスは改善していく必要があります。 アプローチ 上記の課題に対して、テックブログ編集部では以下の対策を考えました。 レビュアーガチャ 技術の詳細な内容はともかく、 発信して問題ない内容や文章になっているかは編集部員がランダムで担当する のがいいのでは? 気づきにくいメールやレビュー管理画面ではなく、 メンションで通知を飛ばせるTeamsで通知する形にしたい 。 今回は、これらの仕組みをPower Autometeの クラウド フローを使って実現します。具体的には、「テックブログのレビュー依頼メール」が飛んできたときにTeamsのチャネル上でランダムにメンションするフローを作成します。 Power Autometeのフローについては 根本さんの記事 をご覧ください。 レビュアーガチャの仕組み 「テックブログのレビュー依頼メール」は以下のようなものです。ISID テックブログ編集部ではレビュー管理にShodoを採用しており、執筆完了の折にはメールが自動配信されます。 このとき、メール本文には「記事のタイトル/レビューリンク」が、メールの メタデータ には「差出人」が含まれています。 レビューに必要な情報はメールに含まれているため、メールの情報を解析して必要な情報のみ抽出します。 後は、 ガチャ要素として編集部員のメンバーから1人ランダムに割り当てます 。これは、編集部員のメンバーの一覧を取得して乱数で選択することで実現することができます。 仕組みは以下の通りです。 上記をPower Automate上でフローとして実装していきました(フロー全体のスクショは長すぎたため割愛します)。 発行すると、レビュー依頼メール受信時にランダムでメンションされるようになります。これにより、 レビュー担当予定者者が明確になり、レビュー割り当て時にはメンション付きで通知されるようになりました! おわりに 本記事では、テックブログ編集部の施策例としてレビュアーガチャが実現するまでの流れをご紹介しました。 今回は触れませんでしたが、上記のようないかにもテックブログらしい施策だけではなく、執筆マニュアルや相談窓口の設置などといったレビューフロー全体の整備を行っています。ISID テックブログでは、今後も執筆者の方がいきいきと執筆できる環境づくりを目指して取り組みを進めていきます。 執筆: @wakamoto.ryosuke 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは、X イノベーション 本部ソフトウェアデザインセンターの中村です。 本記事は 電通国際情報サービス Advent Calendar 2023 の12月8日の記事です。 皆さんはデータベースアクセスを行うアプリケーションの ユニットテスト やインテグレーションテストをどのように実施していますか?絶対の正解はありませんが、テストの効率性や正確性などを考慮して様々な工夫や検討がなされる領域だと思います。例えば次のような戦略があります。 データベースアクセス部分をモックにする 運用時のデータベースよりも高速なインメモリデータベースを使う(例えば H2 Database Engine など) 開発者のローカル環境に運用環境と同等のテスト用データベースをインストールする 本記事では、 Testcontainers を使ってデータベースアクセスを伴うテストを効率的に実施する方法をコードを交えながら紹介します。 本記事で伝えたいこと 最初に、本記事で伝えたいことを簡単にまとめておきます。 TestContainersの API よりも JDBC の接続URLを使おう データベースはデーモンモードで起動しよう テスト同士が影響しないようにテスト毎に ロールバック を実行しよう Testcontainersとは? Testcontainersは、Dockerコンテナで実行できるデータベースなどのソフトウェアに関して軽量で使い捨て可能な インスタンス を提供するための オープンソース フレームワーク です。 この フレームワーク を使うと、運用環境と同等のデータベース インスタンス をテスト環境で何度でも簡単に再現し利用できます。 Testcontainersでは、 Java 、Go、.NET、Node.jsなど多数の プログラミング言語 がサポートされています。 前提 本記事の前提は以下のとおりです。 Testcontainers for Java を使う Dockerを動かす環境としてはDocker Desktopを使う プログラミング言語 にはKotlinを使う データベースには PostgreSQL を使う データベースアクセスの フレームワーク には Komapper を使う ビルドツールにはGradle Wrapperを使う(Gradleのバージョンは以下のとおり) ./gradlew -v ------------------------------------------------------------ Gradle 8.5 ------------------------------------------------------------ Build time: 2023-11-29 14:08:57 UTC Revision: 28aca86a7180baa17117e0e5ba01d8ea9feca598 Kotlin: 1.9.20 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 21.0.1 (Azul Systems, Inc. 21.0.1+12-LTS) OS: Mac OS X 12.6 x86_64 データベースアクセスの フレームワーク であるKomapperについて少し補足をします。Komapperは、 MySQL 、 PostgreSQL 、 Oracle Databaseなどの代表的なリレーショナルデータベースに対応した O/Rマッピング フレームワーク です。Kotlin製の代表的な O/Rマッピング フレームワーク である Exposed ほどの 知名度 はありませんが機能性や使いやすさでは負けていません(と、Komapperの作者である私は考えています😁)。Komapperでは様々なデータベースとの稼働確認をTestcontainersを用いて行っています。 本記事にはデータベース、 フレームワーク 、ビルドツールに強く依存した説明はありませんので適宜お好みの環境に置き換えてお読みいただけます。ただし、 プログラミング言語 については JVM 言語以外に読み替えるのは難しいかもしれません。 本記事で示すサンプルコードは Getting started with Testcontainers for Java を下敷きにしています。ぜひ見比べてみてください。 Gradleの設定 以下のようなbuild.gradle.ktsを用意します。 plugins { application id( "com.google.devtools.ksp" ) version "1.9.21-1.0.15" kotlin( "jvm" ) version "1.9.21" } group = "org.example" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { // Komapperの設定 platform( "org.komapper:komapper-platform:1.15.0" ).let { implementation(it) ksp(it) } implementation( "org.komapper:komapper-starter-jdbc" ) implementation( "org.komapper:komapper-dialect-postgresql-jdbc" ) ksp( "org.komapper:komapper-processor" ) // Testcontainersの設定 testImplementation(platform( "org.testcontainers:testcontainers-bom:1.19.3" )) testRuntimeOnly( "org.testcontainers:postgresql" ) // JUnitの設定 testImplementation( "org.junit.jupiter:junit-jupiter-api:5.9.2" ) testRuntimeOnly( "org.junit.jupiter:junit-jupiter-engine:5.9.2" ) } tasks.getByName<Test>( "test" ) { useJUnitPlatform() // GradleのプロパティをJavaのシステムプロパティに引き継ぐ val jdbcUrl = project.property( "jdbc.url" ) ?: error( "jdbc.url not found" ) systemProperty( "jdbc.url" , jdbcUrl) } ポイントは以下のとおりです。 利用するライブラリ(Kotlin、Komapper、Testcontainers、 JUnit )への依存を示す JDBC の接続URLをGradleのプロパティを介してアプリケーションに渡す ビジネスロジック データベース上の customers テーブルに対応する Customer エンティティクラスを定義します。 package example import org.komapper. annotation .KomapperEntity import org.komapper. annotation .KomapperId import org.komapper. annotation .KomapperTable @KomapperEntity @KomapperTable ( "customers" ) data class Customer( @KomapperId val id: Long , val name: String ) Komapperが認識できるようにKomapperの アノテーション を付与しています。 Customer エンティティクラスを使ってデータの追加や取得を行うサービスクラスは次のようになります。 package example import org.komapper.core.dsl.Meta import org.komapper.core.dsl.QueryDsl import org.komapper.jdbc.JdbcDatabase class CustomerService( private val db: JdbcDatabase) { // Customerエンティティクラスのメタモデル private val c = Meta.customer fun createCustomer(customer: Customer) { db.runQuery { // 対応するSQL: insert into customers (id, name) values (?, ?) QueryDsl.insert(c).single(customer) } } fun getAllCustomers(): List <Customer> { return db.runQuery { // 対応するSQL: select t0_.id, t0_.name from customers as t0_ QueryDsl.from(c) } } } データの追加や取得にはKomapperの API を使います。 API から発行する SQL はコード内のコメントに示したとおりです。 テストコード Getting started with Testcontainers for Java では、テストを制御するコードとテストコードが同じクラスに記述されていますが、本記事では分離して示します。 テストを制御するためのコード テストを制御するコードは以下のようになります。このコードは一度書けば複数のテストクラスから再利用できます。 package example import org.junit.jupiter.api.extension.AfterTestExecutionCallback import org.junit.jupiter.api.extension.BeforeAllCallback import org.junit.jupiter.api.extension.BeforeTestExecutionCallback import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver import org.komapper.core.dsl.Meta import org.komapper.core.dsl.QueryDsl import org.komapper.jdbc.JdbcDatabase import org.komapper.tx.core.TransactionProperty import org.komapper.tx.jdbc.JdbcTransactionSession class DatabaseTestSupport : BeforeAllCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver { companion object { @Volatile private var initialized: Boolean = false // Komapperのデータベース private val db = JdbcDatabase(url = System.getProperty( "jdbc.url" ) ?: error( "jdbc.url not found" )) // Komapperのトランザクションマネージャー private val transactionManager = run { val session = db.config.session as JdbcTransactionSession session.transactionManager } } // 最初のテストを実行する前に一度だけ cusotmers テーブルを作成する override fun beforeAll(context: ExtensionContext) { if ( ! initialized) { initialized = true db.runQuery { // 対応するSQL: create table if not exists customers (id bigint not null, name text not null, constraint pk_customers primary key(id)) QueryDsl.create(Meta.customer) } } } // テストメソッドの実行前にトランザクションを開始する override fun beforeTestExecution(context: ExtensionContext) { transactionManager.begin(TransactionProperty.Name(context.displayName)) } // テストメソッドの実行後にトランザクションをロールバックする override fun afterTestExecution(context: ExtensionContext) { transactionManager.rollback() } // テストクラスのコンストラクタの型をチェックする override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext, ): Boolean = parameterContext.parameter.type === JdbcDatabase :: class .java // テストクラスのコンストラクタに db を渡す override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext, ): Any = db } このクラスのポイントは次のとおりです。 システムプロ パティから JDBC 接続URLを取得しKomapperのデータベース インスタンス を作成する データベース インスタンス はテストクラスのコンスト ラク タに渡す 最初のテストを実行する前に一度だけ cusotmers テーブルを作成する テストメソッドの実行前に トランザクション を開始し、終了後に ロールバック を行う(テスト間の影響を生じさせない) ビジネスロジック のテストコード ビジネスロジック のテストコードは次のようになります。 package example import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.komapper.jdbc.JdbcDatabase @ExtendWith (DatabaseTestSupport :: class ) class CustomerServiceTest(db: JdbcDatabase) { // テスト対象のサービスクラスをインスタンス化する private val customerService = CustomerService(db) @Test fun shouldGetCustomers() { customerService.createCustomer(Customer( 1L , "George" )) customerService.createCustomer(Customer( 2L , "John" )) val customers = customerService.getAllCustomers() assertEquals( 2 , customers.size) } } このクラスのポイントは次のとおりです @ExtendWith で上述した DatabaseTestSupport クラスを指定する コンスト ラク タでデータベース インスタンス を受け取りサービスクラスのコンスト ラク タに渡す テストメソッドの shouldGetCustomers では Customer エンティティを2件追加した後で全件を取得し、取得した件数が2件であったかどうかを検証しています。 プロパティの設定 gradle.propetiesに jdbc .urlをキーとして JDBC の接続URLを設定します。 jdbc.url=jdbc:tc:postgresql:15-alpine:///test?TC_DAEMON=true 接続URLのポイントは次のとおりです。 tc: がTestcontainersを使うことを示す postgresql:15-alpine で利用するデータベースとDockerイメージが決まる test は接続先のデータベースの名前(Testcontainersのデフォルトのデータベース名) TC_DAEMON=true はデータベースをデーモンモードで動かすことを示す Testcontainersは JDBC ドライバが接続URLから自動検出される機能を使ってコンテナを自動で起動します。 データベースをデーモンモードで動かすと JVM がシャットダウンするまで起動したままにできます。つまり、テストメソッドやテストクラスごとにデータベースインスタを起動/停止するよりもテストの実行時間を短縮できます。 テストの実行 以下のコマンドをターミナルに打ち込むことでテストを実行できます。このとき、gradle.propetiesに記載した jdbc.url プロパティが暗黙的に利用されます。 ./gradlew test 利用するDockerイメージは簡単に変更できます。例えば、latestを使いたい場合はプロパティで次のように接続URLを明示的に指定します。 ./gradlew test -Pjdbc . url =jdbc:tc:postgresql:latest:/// test ? TC_DAEMON =true 通常の接続URLを指定すれば、Testcontaners上のデータベースではなくローカルのデータベースに繋ぐこともできます。 ./gradlew test -Pjdbc . url =jdbc:postgresql://localhost:5432/komapper? user =postgres テストが成功したことの確認 テストが成功すると「BUILD SUCCESSFUL」の文字がコンソールに出力されます。ただこれだけでは本当にテストが実施されたのか、テストが成功したのか確証を持てないかもしれません。その場合は以下のコマンドを打つなどしてテストで生成されるレポートをブラウザで開きましょう。 open build/reports/tests/ test /classes/example.CustomerServiceTest.html ブラウザ上で以下のような表示を確認できれば間違いなくテストが成功していると言えます。 上記ではGradleコマンドをターミナルに打ち込んでテストを実行すると説明しましたが、 IntelliJ IDEAなどの IDE からテストを実行しても構いません。その場合、次のような表示を IDE 上で確認できればテストが成功しています。 本記事で伝えたいこと(再) Testcontainersのオススメの使い方をサンプルコードを交えて紹介しました。 以下、改めて本記事で伝えたい3点について説明をします。 TestContainersの API よりも JDBC の接続URLを使おう Testcontainersの API を直接呼びだすサンプルコードをよく見かけますが、Testcontainersは JDBC の接続URLから特定のデータベースを起動できます。接続URLを使うことでDockerイメージを切り替えたりローカルの環境に接続したりが簡単になります。 なお、Testcontainersの API を直接呼びだす方法を否定したいわけではありません。要件によっては直接 API を呼びだす方法のほうが使いやすいこともあるでしょう。 データベースはデーモンモードで起動しよう 接続URLのオプションで TC_DAEMON=true パラメータを指定するとデーモンモードで起動できます。デーモンモードにするとデータベース インスタンス の起動と終了をテストを通して一度きりにできるのでテストの実行時間を短縮できます。 Testcontainersの API を直接呼びだす場合は、 Singleton containers に記載の方法で同等のことが実現できます。 テスト同士が影響しないようにテスト毎に ロールバック を実行しよう デーモンモードで起動すると、あるテストにおけるデータベースの変更が他のテストに影響することがあり得ます。テスティング フレームワーク やデータベースアクセスの フレームワーク を組み合わせて、テスト実行前の トランザクション 開始と終了時の ロールバック を徹底しましょう。 なお、テストによってはシーケンスのインクリメントや DDL の実行など ロールバック できない変更を加えることがあるかもしれません。その場合は別途対応が必要になります。 最後に 本記事で紹介したコードは GitHub Actions上で特別な設定をすることなく動作します。サンプルコードを含めた リポジトリ https://github.com/nakamura-to/testcontainers-demo には、 GitHub Actionsのワークフローも含めています。参考にしていただければ幸いです。 本記事をお読みいただきありがとうございました。 私たちの アドベントカレンダー では土曜日と日曜日はお休みしているので、次回の記事は月曜日に公開予定です。12月11日、月曜日の記事もぜひご覧ください。 私たちは一緒に働いてくれる仲間を募集しています! フルサイクルエンジニア 執筆: @nakamura.toshihiro 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
こんにちは、X イノベーション 本部ソフトウェアデザインセンターの中村です。 本記事は 電通国際情報サービス Advent Calendar 2023 の12月8日の記事です。 皆さんはデータベースアクセスを行うアプリケーションの ユニットテスト やインテグレーションテストをどのように実施していますか?絶対の正解はありませんが、テストの効率性や正確性などを考慮して様々な工夫や検討がなされる領域だと思います。例えば次のような戦略があります。 データベースアクセス部分をモックにする 運用時のデータベースよりも高速なインメモリデータベースを使う(例えば H2 Database Engine など) 開発者のローカル環境に運用環境と同等のテスト用データベースをインストールする 本記事では、 Testcontainers を使ってデータベースアクセスを伴うテストを効率的に実施する方法をコードを交えながら紹介します。 本記事で伝えたいこと 最初に、本記事で伝えたいことを簡単にまとめておきます。 TestContainersの API よりも JDBC の接続URLを使おう データベースはデーモンモードで起動しよう テスト同士が影響しないようにテスト毎に ロールバック を実行しよう Testcontainersとは? Testcontainersは、Dockerコンテナで実行できるデータベースなどのソフトウェアに関して軽量で使い捨て可能な インスタンス を提供するための オープンソース フレームワーク です。 この フレームワーク を使うと、運用環境と同等のデータベース インスタンス をテスト環境で何度でも簡単に再現し利用できます。 Testcontainersでは、 Java 、Go、.NET、Node.jsなど多数の プログラミング言語 がサポートされています。 前提 本記事の前提は以下のとおりです。 Testcontainers for Java を使う Dockerを動かす環境としてはDocker Desktopを使う プログラミング言語 にはKotlinを使う データベースには PostgreSQL を使う データベースアクセスの フレームワーク には Komapper を使う ビルドツールにはGradle Wrapperを使う(Gradleのバージョンは以下のとおり) ./gradlew -v ------------------------------------------------------------ Gradle 8.5 ------------------------------------------------------------ Build time: 2023-11-29 14:08:57 UTC Revision: 28aca86a7180baa17117e0e5ba01d8ea9feca598 Kotlin: 1.9.20 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 21.0.1 (Azul Systems, Inc. 21.0.1+12-LTS) OS: Mac OS X 12.6 x86_64 データベースアクセスの フレームワーク であるKomapperについて少し補足をします。Komapperは、 MySQL 、 PostgreSQL 、 Oracle Databaseなどの代表的なリレーショナルデータベースに対応した O/Rマッピング フレームワーク です。Kotlin製の代表的な O/Rマッピング フレームワーク である Exposed ほどの 知名度 はありませんが機能性や使いやすさでは負けていません(と、Komapperの作者である私は考えています😁)。Komapperでは様々なデータベースとの稼働確認をTestcontainersを用いて行っています。 本記事にはデータベース、 フレームワーク 、ビルドツールに強く依存した説明はありませんので適宜お好みの環境に置き換えてお読みいただけます。ただし、 プログラミング言語 については JVM 言語以外に読み替えるのは難しいかもしれません。 本記事で示すサンプルコードは Getting started with Testcontainers for Java を下敷きにしています。ぜひ見比べてみてください。 Gradleの設定 以下のようなbuild.gradle.ktsを用意します。 plugins { application id( "com.google.devtools.ksp" ) version "1.9.21-1.0.15" kotlin( "jvm" ) version "1.9.21" } group = "org.example" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { // Komapperの設定 platform( "org.komapper:komapper-platform:1.15.0" ).let { implementation(it) ksp(it) } implementation( "org.komapper:komapper-starter-jdbc" ) implementation( "org.komapper:komapper-dialect-postgresql-jdbc" ) ksp( "org.komapper:komapper-processor" ) // Testcontainersの設定 testImplementation(platform( "org.testcontainers:testcontainers-bom:1.19.3" )) testRuntimeOnly( "org.testcontainers:postgresql" ) // JUnitの設定 testImplementation( "org.junit.jupiter:junit-jupiter-api:5.9.2" ) testRuntimeOnly( "org.junit.jupiter:junit-jupiter-engine:5.9.2" ) } tasks.getByName<Test>( "test" ) { useJUnitPlatform() // GradleのプロパティをJavaのシステムプロパティに引き継ぐ val jdbcUrl = project.property( "jdbc.url" ) ?: error( "jdbc.url not found" ) systemProperty( "jdbc.url" , jdbcUrl) } ポイントは以下のとおりです。 利用するライブラリ(Kotlin、Komapper、Testcontainers、 JUnit )への依存を示す JDBC の接続URLをGradleのプロパティを介してアプリケーションに渡す ビジネスロジック データベース上の customers テーブルに対応する Customer エンティティクラスを定義します。 package example import org.komapper. annotation .KomapperEntity import org.komapper. annotation .KomapperId import org.komapper. annotation .KomapperTable @KomapperEntity @KomapperTable ( "customers" ) data class Customer( @KomapperId val id: Long , val name: String ) Komapperが認識できるようにKomapperの アノテーション を付与しています。 Customer エンティティクラスを使ってデータの追加や取得を行うサービスクラスは次のようになります。 package example import org.komapper.core.dsl.Meta import org.komapper.core.dsl.QueryDsl import org.komapper.jdbc.JdbcDatabase class CustomerService( private val db: JdbcDatabase) { // Customerエンティティクラスのメタモデル private val c = Meta.customer fun createCustomer(customer: Customer) { db.runQuery { // 対応するSQL: insert into customers (id, name) values (?, ?) QueryDsl.insert(c).single(customer) } } fun getAllCustomers(): List <Customer> { return db.runQuery { // 対応するSQL: select t0_.id, t0_.name from customers as t0_ QueryDsl.from(c) } } } データの追加や取得にはKomapperの API を使います。 API から発行する SQL はコード内のコメントに示したとおりです。 テストコード Getting started with Testcontainers for Java では、テストを制御するコードとテストコードが同じクラスに記述されていますが、本記事では分離して示します。 テストを制御するためのコード テストを制御するコードは以下のようになります。このコードは一度書けば複数のテストクラスから再利用できます。 package example import org.junit.jupiter.api.extension.AfterTestExecutionCallback import org.junit.jupiter.api.extension.BeforeAllCallback import org.junit.jupiter.api.extension.BeforeTestExecutionCallback import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver import org.komapper.core.dsl.Meta import org.komapper.core.dsl.QueryDsl import org.komapper.jdbc.JdbcDatabase import org.komapper.tx.core.TransactionProperty import org.komapper.tx.jdbc.JdbcTransactionSession class DatabaseTestSupport : BeforeAllCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver { companion object { @Volatile private var initialized: Boolean = false // Komapperのデータベース private val db = JdbcDatabase(url = System.getProperty( "jdbc.url" ) ?: error( "jdbc.url not found" )) // Komapperのトランザクションマネージャー private val transactionManager = run { val session = db.config.session as JdbcTransactionSession session.transactionManager } } // 最初のテストを実行する前に一度だけ cusotmers テーブルを作成する override fun beforeAll(context: ExtensionContext) { if ( ! initialized) { initialized = true db.runQuery { // 対応するSQL: create table if not exists customers (id bigint not null, name text not null, constraint pk_customers primary key(id)) QueryDsl.create(Meta.customer) } } } // テストメソッドの実行前にトランザクションを開始する override fun beforeTestExecution(context: ExtensionContext) { transactionManager.begin(TransactionProperty.Name(context.displayName)) } // テストメソッドの実行後にトランザクションをロールバックする override fun afterTestExecution(context: ExtensionContext) { transactionManager.rollback() } // テストクラスのコンストラクタの型をチェックする override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext, ): Boolean = parameterContext.parameter.type === JdbcDatabase :: class .java // テストクラスのコンストラクタに db を渡す override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext, ): Any = db } このクラスのポイントは次のとおりです。 システムプロ パティから JDBC 接続URLを取得しKomapperのデータベース インスタンス を作成する データベース インスタンス はテストクラスのコンスト ラク タに渡す 最初のテストを実行する前に一度だけ cusotmers テーブルを作成する テストメソッドの実行前に トランザクション を開始し、終了後に ロールバック を行う(テスト間の影響を生じさせない) ビジネスロジック のテストコード ビジネスロジック のテストコードは次のようになります。 package example import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.komapper.jdbc.JdbcDatabase @ExtendWith (DatabaseTestSupport :: class ) class CustomerServiceTest(db: JdbcDatabase) { // テスト対象のサービスクラスをインスタンス化する private val customerService = CustomerService(db) @Test fun shouldGetCustomers() { customerService.createCustomer(Customer( 1L , "George" )) customerService.createCustomer(Customer( 2L , "John" )) val customers = customerService.getAllCustomers() assertEquals( 2 , customers.size) } } このクラスのポイントは次のとおりです @ExtendWith で上述した DatabaseTestSupport クラスを指定する コンスト ラク タでデータベース インスタンス を受け取りサービスクラスのコンスト ラク タに渡す テストメソッドの shouldGetCustomers では Customer エンティティを2件追加した後で全件を取得し、取得した件数が2件であったかどうかを検証しています。 プロパティの設定 gradle.propetiesに jdbc .urlをキーとして JDBC の接続URLを設定します。 jdbc.url=jdbc:tc:postgresql:15-alpine:///test?TC_DAEMON=true 接続URLのポイントは次のとおりです。 tc: がTestcontainersを使うことを示す postgresql:15-alpine で利用するデータベースとDockerイメージが決まる test は接続先のデータベースの名前(Testcontainersのデフォルトのデータベース名) TC_DAEMON=true はデータベースをデーモンモードで動かすことを示す Testcontainersは JDBC ドライバが接続URLから自動検出される機能を使ってコンテナを自動で起動します。 データベースをデーモンモードで動かすと JVM がシャットダウンするまで起動したままにできます。つまり、テストメソッドやテストクラスごとにデータベースインスタを起動/停止するよりもテストの実行時間を短縮できます。 テストの実行 以下のコマンドをターミナルに打ち込むことでテストを実行できます。このとき、gradle.propetiesに記載した jdbc.url プロパティが暗黙的に利用されます。 ./gradlew test 利用するDockerイメージは簡単に変更できます。例えば、latestを使いたい場合はプロパティで次のように接続URLを明示的に指定します。 ./gradlew test -Pjdbc . url =jdbc:tc:postgresql:latest:/// test ? TC_DAEMON =true 通常の接続URLを指定すれば、Testcontaners上のデータベースではなくローカルのデータベースに繋ぐこともできます。 ./gradlew test -Pjdbc . url =jdbc:postgresql://localhost:5432/komapper? user =postgres テストが成功したことの確認 テストが成功すると「BUILD SUCCESSFUL」の文字がコンソールに出力されます。ただこれだけでは本当にテストが実施されたのか、テストが成功したのか確証を持てないかもしれません。その場合は以下のコマンドを打つなどしてテストで生成されるレポートをブラウザで開きましょう。 open build/reports/tests/ test /classes/example.CustomerServiceTest.html ブラウザ上で以下のような表示を確認できれば間違いなくテストが成功していると言えます。 上記ではGradleコマンドをターミナルに打ち込んでテストを実行すると説明しましたが、 IntelliJ IDEAなどの IDE からテストを実行しても構いません。その場合、次のような表示を IDE 上で確認できればテストが成功しています。 本記事で伝えたいこと(再) Testcontainersのオススメの使い方をサンプルコードを交えて紹介しました。 以下、改めて本記事で伝えたい3点について説明をします。 TestContainersの API よりも JDBC の接続URLを使おう Testcontainersの API を直接呼びだすサンプルコードをよく見かけますが、Testcontainersは JDBC の接続URLから特定のデータベースを起動できます。接続URLを使うことでDockerイメージを切り替えたりローカルの環境に接続したりが簡単になります。 なお、Testcontainersの API を直接呼びだす方法を否定したいわけではありません。要件によっては直接 API を呼びだす方法のほうが使いやすいこともあるでしょう。 データベースはデーモンモードで起動しよう 接続URLのオプションで TC_DAEMON=true パラメータを指定するとデーモンモードで起動できます。デーモンモードにするとデータベース インスタンス の起動と終了をテストを通して一度きりにできるのでテストの実行時間を短縮できます。 Testcontainersの API を直接呼びだす場合は、 Singleton containers に記載の方法で同等のことが実現できます。 テスト同士が影響しないようにテスト毎に ロールバック を実行しよう デーモンモードで起動すると、あるテストにおけるデータベースの変更が他のテストに影響することがあり得ます。テスティング フレームワーク やデータベースアクセスの フレームワーク を組み合わせて、テスト実行前の トランザクション 開始と終了時の ロールバック を徹底しましょう。 なお、テストによってはシーケンスのインクリメントや DDL の実行など ロールバック できない変更を加えることがあるかもしれません。その場合は別途対応が必要になります。 最後に 本記事で紹介したコードは GitHub Actions上で特別な設定をすることなく動作します。サンプルコードを含めた リポジトリ https://github.com/nakamura-to/testcontainers-demo には、 GitHub Actionsのワークフローも含めています。参考にしていただければ幸いです。 本記事をお読みいただきありがとうございました。 私たちの アドベントカレンダー では土曜日と日曜日はお休みしているので、次回の記事は月曜日に公開予定です。12月11日、月曜日の記事もぜひご覧ください。 私たちは一緒に働いてくれる仲間を募集しています! フルサイクルエンジニア 執筆: @nakamura.toshihiro 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
こんにちは。金融ソリューション事業部の宮原です。本記事は 電通国際情報サービス Advent Calendar 2023 7日目の記事となります。 みなさん、Go言語で並行処理は利用されているでしょうか?Go言語はゴルーチンやチャネルといった独自の並行処理機構を備えており、比較的簡単に並行処理を導入できます。 しかしながら、ゴルーチンやチャネルの仕組みを理解し、並行処理のパターンを独自に実装していくのは意外と難しいものです。 Go言語ではsyncパッケージをはじめとして、並行処理のパターンを簡単に導入できるようにライブラリとして提供しています。 本記事ではGo言語が提供しているライブラリの中でも準標準パッケージの位置付けである golang.org/x/sync/errgroup パッケージを利用した並行処理の実装方法をご紹介したいと思います。 前提 まずはシンプルに記述してみる エラーを考慮しながら関数化してみる キャンセルを検討してみる 同時実行数を制御する まとめ 前提 以降では「特定のURLにHTTPリク エス トを並行で送信しながら、全てのURLへのHTTPリク エス トが終わるまで待ち合わせる」という処理を前提としてサンプルコードを記載します。 今回のサンプルコードはsyncパッケージの サンプルコード を一部改変して作成しています。 また、Goのバージョンについては1.21.3を前提としています。 まずはシンプルに記述してみる package main import ( "fmt" "net/http" "sync" ) func main() { var wg sync.WaitGroup var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } for i, url := range urls { i, url := i, url wg.Add( 1 ) go func () { defer wg.Done() resp, err := http.Get(url) if err != nil { fmt.Println(err) return } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) }() } wg.Wait() } まずはシンプルな形で処理を実装してみます。最初の例ではerrgroupパッケージは利用せず、syncパッケージのsync.WaitGroupを利用して処理を記述します。 後ほど詳しく説明しますが、errgroupパッケージはsync.WaitGroupの機能を拡張したものになります。そのため、まずはsync.WaitGroupをよく理解することが重要です。 処理としてはforループとgoキーワードを利用しゴルーチンを起動して、3つのURLに対して並行にリク エス トを送信しています。 sync.WaitGroupを理解する上で重要なのはAddメソッド、Doneメソッド、Waitメソッドです。 今回のプログラムではgoキーワードでゴルーチンを起動する前にAddメソッドを呼び出し、処理が終わった後にDoneメソッドを呼び出しています。 そしてループの外側でWaitメソッドを呼び出し、すべてのゴルーチンが完了するのを待ちます。 sync.WaitGroupではAddメソッドで内部のカウンタをインクリメントし、Doneメソッドで内部のカウンタをデクリメントします。 そしてWaitメソッドでは内部のカウンタを監視し、カウンタが0になるまで待つようになっています。 実行結果は以下です。今回の実行では2番目、3番目、1番目の順で処理が完了したようです。処理が並行に行われているため、この出力は実行毎に変わる可能性があります。 2 番目のリクエスト: 200 OK 3 番目のリクエスト: 200 OK 1 番目のリクエスト: 200 OK sync.WaitGroupを利用するとシンプルに並行処理を記述できます。チャネルの処理を考慮する必要がなく、処理の流れも比較的イメージしやすいです。 エラーを考慮しながら関数化してみる 先ほどの例ではsync.WaitGroupを利用して処理を記述しました。 今度はURLを並行で処理する部分を関数化する例を考えてみましょう。 実装している中で特定の処理を関数化したくなったり、エラーハンドリングをしたくなったりすることはよくあります。 今回は以下のような関数を考えてみます func CallURL(urls [] string ) error リク エス ト先のURLを引数にとり、URLに対して並行に処理を行うようなCallURLという関数を考えてみます。 また、関数内でエラーが発生した場合にはエラーを上位の関数に渡します。 しかし悩ましいのがエラーの扱いです。goキーワードを使った関数から直接エラーを受け取ることはできませんし、sync.WaitGroupのWaitメソッドを利用してエラーを取得することもできません。 そこで便利なのが golang.org/x/sync/errgroup パッケージのerrgroup.Groupです。 errgroup.Groupを利用することでsync.WaitGroupと同等の機能を利用しながらエラーハンドリングを行うことが可能です。このerrgroup.Groupを利用して上記の関数を記述してみます。 package main import ( "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { var eg errgroup.Group for i, url := range urls { i, url := i, url eg.Go( func () error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } errgroup.Groupはsync.WaitGroupを内部に持つ構造体です。 errgroup.Groupを利用した今回のサンプルコードではAddメソッド、Doneメソッドやゴルーチンの起動処理は記載していません。 これらの処理は全てGoメソッドの中で呼び出されているため、明示的に呼びだす必要はありません。 Goメソッドに渡した関数は内部でgoキーワードを使って並行に起動するようになっています。 また、Goメソッドにはエラーを返り値とした関数を渡し、Waitメソッドでは関数からのエラーを受け取れるようになっています。Goメソッドで起動した関数の中でエラーが発生した場合にWaitメソッド経由でエラーを受け取ることができます。 ここでポイントなのが、複数のエラーが発生した場合でもWaitメソッドで確認できるエラーは一つだけということです。 Waitメソッドでは複数の関数の中で最初に発生したエラーしか確認することができず、その他のエラーについては確認することはできません。 キャンセルを検討してみる 先ほどの例では一つの関数でエラーが発生しても、他の関数はそのエラーを気にせず処理を続けるようになっていました。 しかしながら場合によってはエラーが発生した場合に他の関数の処理を停止させたい、キャンセルしたい場合もあると思います。 実はキャンセルに関する機能もerrgroup.Groupには備わっています。 一つの関数でエラーが発生した場合に、他の関数の処理を停止するサンプルを作成してみます。 package main import ( "context" "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { eg, ctx := errgroup.WithContext(context.Background()) for i, url := range urls { i, url := i, url eg.Go( func () error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil ) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } 今回の例ではerrgroup.WithContext関数を最初に呼び出しています。 返り値はerrgroup.Groupとcontext.Contextです。 あとは返り値のerrgroup.Groupとcontext.Contextを利用して、処理を実装します。 今回の例では API 呼び出し部分も少し変わっています。htttp.NewRequestWithContext関数とhttp.DefaultClient.Doメソッドを呼びだすようにしています。 これらの関数を利用することで引数のcontext.Contextがキャンセルされているかを監視しながら処理を進めるようにしています。 http.DefaultClient.Doメソッド内部ではcontext.Contextのキャンセルを監視し、キャンセルされている場合は処理を終了するようになっています。 では肝心のcontext.Contextのキャンセル処理はどこで実行されているのでしょうか? 実はGoメソッドの内部で関数がエラーを返した場合にcontext.Contextのキャンセルを実行するようになっています。 WithContext関数の中ではcontext.WithCancelCause関数が呼び出されており、WithCancelCause関数の返り値のキャンセル関数がerrgroup.Groupのフィールドにセットされます。 Goメソッドの内部ではエラーが発生した場合に、このキャンセル関数を呼びだすことでcontext.Contextをキャンセルするようになっています。 (Goのバージョンが1.20以上の場合、WithContext関数の内部ではcontext.WithCancelCause関数が呼び出されますが、1.20以前の場合はcontext.WithCancel関数が呼び出されます) 同時実行数を制御する errgroup.GroupにはGoメソッドで起動するゴルーチンの同時実行数を制御する機能があります。 例えば特定のURLに複数のリク エス トを投げる際に、負荷を考慮しながらリク エス トを投げたい場合などがあると思います。そういった際にはこの同時実行数制御の仕組みが役に立ちます。 サンプルコードを見てみましょう。 package main import ( "context" "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { eg, ctx := errgroup.WithContext(context.Background()) eg.SetLimit( 2 ) for i, url := range urls { i, url := i, url eg.Go( func () error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil ) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } 一つ前の例から変わっている箇所は一箇所です。 今回の例ではerrgroup.GroupのSetLimitメソッドを呼び出しています。引数で同時実行数を渡すと、Goメソッド内で起動されるゴルーチンの数が制限されます。今回のサンプルコードではゴルーチンの同時実行数を2としています。 errgroup.Groupの内部ではバッファ付きチャネルを利用して、ゴルーチンの同時実行数を制御しています。 Goメソッド内部でゴルーチン起動前にバッファ付きチャネルに値を追加し、処理完了後にチャネルから値を取り出しています。バッファがいっぱいになった場合はそのタイミングで処理が停止するようになっています。 このようにSetLimitメソッドを使うだけでゴルーチンの同時実行数を制御できます。 まとめ 今回の記事では golang.org/x/sync/errgroup パッケージを利用した並行処理の実装について紹介しました。 errgroupパッケージを利用することで簡単に並行処理を記述できるだけではなくエラーハンドリング、キャンセル処理、ゴルーチンの同時実行数制御など並行処理のパターンを導入できます。 みなさんも並行処理を実装する際にはぜひ golang.org/x/sync/errgroup パッケージの利用を検討してみてください。 私たちは一緒に働いてくれる仲間を募集しています! 募集職種一覧 執筆: @miyahara.hikaru 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは。金融ソリューション事業部の宮原です。本記事は 電通国際情報サービス Advent Calendar 2023 7日目の記事となります。 みなさん、Go言語で並行処理は利用されているでしょうか?Go言語はゴルーチンやチャネルといった独自の並行処理機構を備えており、比較的簡単に並行処理を導入できます。 しかしながら、ゴルーチンやチャネルの仕組みを理解し、並行処理のパターンを独自に実装していくのは意外と難しいものです。 Go言語ではsyncパッケージをはじめとして、並行処理のパターンを簡単に導入できるようにライブラリとして提供しています。 本記事ではGo言語が提供しているライブラリの中でも準標準パッケージの位置付けである golang.org/x/sync/errgroup パッケージを利用した並行処理の実装方法をご紹介したいと思います。 前提 まずはシンプルに記述してみる エラーを考慮しながら関数化してみる キャンセルを検討してみる 同時実行数を制御する まとめ 前提 以降では「特定のURLにHTTPリク エス トを並行で送信しながら、全てのURLへのHTTPリク エス トが終わるまで待ち合わせる」という処理を前提としてサンプルコードを記載します。 今回のサンプルコードはsyncパッケージの サンプルコード を一部改変して作成しています。 また、Goのバージョンについては1.21.3を前提としています。 まずはシンプルに記述してみる package main import ( "fmt" "net/http" "sync" ) func main() { var wg sync.WaitGroup var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } for i, url := range urls { i, url := i, url wg.Add( 1 ) go func () { defer wg.Done() resp, err := http.Get(url) if err != nil { fmt.Println(err) return } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) }() } wg.Wait() } まずはシンプルな形で処理を実装してみます。最初の例ではerrgroupパッケージは利用せず、syncパッケージのsync.WaitGroupを利用して処理を記述します。 後ほど詳しく説明しますが、errgroupパッケージはsync.WaitGroupの機能を拡張したものになります。そのため、まずはsync.WaitGroupをよく理解することが重要です。 処理としてはforループとgoキーワードを利用しゴルーチンを起動して、3つのURLに対して並行にリク エス トを送信しています。 sync.WaitGroupを理解する上で重要なのはAddメソッド、Doneメソッド、Waitメソッドです。 今回のプログラムではgoキーワードでゴルーチンを起動する前にAddメソッドを呼び出し、処理が終わった後にDoneメソッドを呼び出しています。 そしてループの外側でWaitメソッドを呼び出し、すべてのゴルーチンが完了するのを待ちます。 sync.WaitGroupではAddメソッドで内部のカウンタをインクリメントし、Doneメソッドで内部のカウンタをデクリメントします。 そしてWaitメソッドでは内部のカウンタを監視し、カウンタが0になるまで待つようになっています。 実行結果は以下です。今回の実行では2番目、3番目、1番目の順で処理が完了したようです。処理が並行に行われているため、この出力は実行毎に変わる可能性があります。 2 番目のリクエスト: 200 OK 3 番目のリクエスト: 200 OK 1 番目のリクエスト: 200 OK sync.WaitGroupを利用するとシンプルに並行処理を記述できます。チャネルの処理を考慮する必要がなく、処理の流れも比較的イメージしやすいです。 エラーを考慮しながら関数化してみる 先ほどの例ではsync.WaitGroupを利用して処理を記述しました。 今度はURLを並行で処理する部分を関数化する例を考えてみましょう。 実装している中で特定の処理を関数化したくなったり、エラーハンドリングをしたくなったりすることはよくあります。 今回は以下のような関数を考えてみます func CallURL(urls [] string ) error リク エス ト先のURLを引数にとり、URLに対して並行に処理を行うようなCallURLという関数を考えてみます。 また、関数内でエラーが発生した場合にはエラーを上位の関数に渡します。 しかし悩ましいのがエラーの扱いです。goキーワードを使った関数から直接エラーを受け取ることはできませんし、sync.WaitGroupのWaitメソッドを利用してエラーを取得することもできません。 そこで便利なのが golang.org/x/sync/errgroup パッケージのerrgroup.Groupです。 errgroup.Groupを利用することでsync.WaitGroupと同等の機能を利用しながらエラーハンドリングを行うことが可能です。このerrgroup.Groupを利用して上記の関数を記述してみます。 package main import ( "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { var eg errgroup.Group for i, url := range urls { i, url := i, url eg.Go( func () error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } errgroup.Groupはsync.WaitGroupを内部に持つ構造体です。 errgroup.Groupを利用した今回のサンプルコードではAddメソッド、Doneメソッドやゴルーチンの起動処理は記載していません。 これらの処理は全てGoメソッドの中で呼び出されているため、明示的に呼びだす必要はありません。 Goメソッドに渡した関数は内部でgoキーワードを使って並行に起動するようになっています。 また、Goメソッドにはエラーを返り値とした関数を渡し、Waitメソッドでは関数からのエラーを受け取れるようになっています。Goメソッドで起動した関数の中でエラーが発生した場合にWaitメソッド経由でエラーを受け取ることができます。 ここでポイントなのが、複数のエラーが発生した場合でもWaitメソッドで確認できるエラーは一つだけということです。 Waitメソッドでは複数の関数の中で最初に発生したエラーしか確認することができず、その他のエラーについては確認することはできません。 キャンセルを検討してみる 先ほどの例では一つの関数でエラーが発生しても、他の関数はそのエラーを気にせず処理を続けるようになっていました。 しかしながら場合によってはエラーが発生した場合に他の関数の処理を停止させたい、キャンセルしたい場合もあると思います。 実はキャンセルに関する機能もerrgroup.Groupには備わっています。 一つの関数でエラーが発生した場合に、他の関数の処理を停止するサンプルを作成してみます。 package main import ( "context" "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { eg, ctx := errgroup.WithContext(context.Background()) for i, url := range urls { i, url := i, url eg.Go( func () error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil ) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } 今回の例ではerrgroup.WithContext関数を最初に呼び出しています。 返り値はerrgroup.Groupとcontext.Contextです。 あとは返り値のerrgroup.Groupとcontext.Contextを利用して、処理を実装します。 今回の例では API 呼び出し部分も少し変わっています。htttp.NewRequestWithContext関数とhttp.DefaultClient.Doメソッドを呼びだすようにしています。 これらの関数を利用することで引数のcontext.Contextがキャンセルされているかを監視しながら処理を進めるようにしています。 http.DefaultClient.Doメソッド内部ではcontext.Contextのキャンセルを監視し、キャンセルされている場合は処理を終了するようになっています。 では肝心のcontext.Contextのキャンセル処理はどこで実行されているのでしょうか? 実はGoメソッドの内部で関数がエラーを返した場合にcontext.Contextのキャンセルを実行するようになっています。 WithContext関数の中ではcontext.WithCancelCause関数が呼び出されており、WithCancelCause関数の返り値のキャンセル関数がerrgroup.Groupのフィールドにセットされます。 Goメソッドの内部ではエラーが発生した場合に、このキャンセル関数を呼びだすことでcontext.Contextをキャンセルするようになっています。 (Goのバージョンが1.20以上の場合、WithContext関数の内部ではcontext.WithCancelCause関数が呼び出されますが、1.20以前の場合はcontext.WithCancel関数が呼び出されます) 同時実行数を制御する errgroup.GroupにはGoメソッドで起動するゴルーチンの同時実行数を制御する機能があります。 例えば特定のURLに複数のリク エス トを投げる際に、負荷を考慮しながらリク エス トを投げたい場合などがあると思います。そういった際にはこの同時実行数制御の仕組みが役に立ちます。 サンプルコードを見てみましょう。 package main import ( "context" "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { eg, ctx := errgroup.WithContext(context.Background()) eg.SetLimit( 2 ) for i, url := range urls { i, url := i, url eg.Go( func () error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil ) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%d番目のリクエスト: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } 一つ前の例から変わっている箇所は一箇所です。 今回の例ではerrgroup.GroupのSetLimitメソッドを呼び出しています。引数で同時実行数を渡すと、Goメソッド内で起動されるゴルーチンの数が制限されます。今回のサンプルコードではゴルーチンの同時実行数を2としています。 errgroup.Groupの内部ではバッファ付きチャネルを利用して、ゴルーチンの同時実行数を制御しています。 Goメソッド内部でゴルーチン起動前にバッファ付きチャネルに値を追加し、処理完了後にチャネルから値を取り出しています。バッファがいっぱいになった場合はそのタイミングで処理が停止するようになっています。 このようにSetLimitメソッドを使うだけでゴルーチンの同時実行数を制御できます。 まとめ 今回の記事では golang.org/x/sync/errgroup パッケージを利用した並行処理の実装について紹介しました。 errgroupパッケージを利用することで簡単に並行処理を記述できるだけではなくエラーハンドリング、キャンセル処理、ゴルーチンの同時実行数制御など並行処理のパターンを導入できます。 みなさんも並行処理を実装する際にはぜひ golang.org/x/sync/errgroup パッケージの利用を検討してみてください。 私たちは一緒に働いてくれる仲間を募集しています! 募集職種一覧 執筆: @miyahara.hikaru 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
どーもー!X イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの福山です。 本記事は 電通国際情報サービス Advent Calendar 2023 6日目の記事となります。 念願だった AWS re:Invent現地参戦、ついに実現しました! 本レポートでは、セキュリティ関連のセッションに絞って紹介したいと思います。 re:Inventとは セッション内容 Builders' Session セッション名:Patch it up: Building a vulnerability management solution 感想 Chalk Talk セッション名:How to automate incident response with AWS security services & AI/ML 感想 最後に re:Inventとは 年に一度米国ラスベガスで開催される、 AWS 主催のIT業界最大規模のイベントです。 今年は日本から1000人以上、全体の参加者は約4〜5万人とのことで、規模の大きさがわかるかと思います。 re:Invent自体は2000以上のセッションが用意されているのと、 AWS やスポンサーと交流できるEXPOが主な内容です。 セッション内容 Builders' Session Builders' Sessionとは、少人数のグループで実際に手を動かしながら学んでいくタイプのセッションで、所要時間は1時間程度です。 各テーブルに AWS エキスパートがついており、気軽に質問できるのが特徴です。 参加したBuilders' Sessionのうち、1つ紹介します。 セッション名:Patch it up: Building a vulnerability management solution セッション概要: With thousands of new common vulnerabilities and exposures discovered every month, organizations can find it challenging to keep on top of their security using traditional, on-premises patching solutions. And the shift to containers and serverless can seem complex in an already diverse IT environment. Join this builders’ session to take a deep dive into patch management across a hybrid landscape with scenarios played out for containers, serverless, and virtual machines using AWS services. You must bring your laptop to participate. 複数タイプのリソースを 脆弱性 管理する方法を学ぶ。 Cloud9上でCDKの実行環境を構築して実行し、下図のような 脆弱性 管理の仕組みを構築していく。 ここでの 脆弱性 管理というのは、パッケージの 脆弱性 、コードの 脆弱性 、ネットワーク構成の 脆弱性 を指している。 リソース別に、EC2、ECR、Lambda、ネットワークは Amazon Inspectorで検出し、オンプレサーバは AWS Systems Manager Patch Managerで検出する。それらのFindingsをSecurity Hubに集約する。 Amazon Inspectorは検知までとなるが、 AWS Systems Manager Patch Managerは検知後のパッチ適用まで自動化することも可能。 感想 複数種類のリソースの 脆弱性 管理をするのは大変ですが、オンプレサーバ+ AWS リソースに限れば、Inspector、Patch Manager、Security Hubでここまでカバーできるんだと知りました。 しかもこれらをCloud9で構築できるから素晴らしいですね。 なお、Patch Mangerによるパッチ適用の自動化に関しては、いきなり本番で実行するのではなく、検証環境を用意して一度試してみた方がよさそうです。 あとは思いつき(要望)ですが、ソフトウェア名とバージョンを手動入力すると CPE (Common Platform Enumeration)が登録され、Inspector等でスキャンができるような機能があれば、管理の幅がさらに広がって最高なんだけどなぁ〜と思ったりしました。 さて、今回参加してみたBuilders' Sessionですが、それとは別で予約がすぐ埋まる人気セッションタイプの一つにWorkshopというものがあります。 WorkshopはBuilders' Sessionの長時間コースという位置付けになります。 Builder's SessionやWorkshopは現地でしか体験できないものなので、ぜひやっておくべきものだと思います。 Chalk Talk Chalk Talk は対話型のセッションです。後日オンラインで視聴できるBreakout Session同様、基本は AWS 側で講演を進めていきますが、参加者側から任意のタイミングで質問できます。そのため、Breakout Sessionより参加人数が制限されています。また、オンライン視聴では参加者からの質問が音声に含まれませんので、全て聞くには現地に参加するしかありません。 参加したChalk Talk のうち、1つを紹介します。 セッション名:How to automate incident response with AWS security services & AI/ML セッション概要: In this chalk talk , explore how generative AI and machine learning can be used alongside key AWS security services to enrich security analytics and automate incident response for security teams, helping to reduce the time to respond to security incidents and improve the developer experience. 顧客から頻繁に話題にあがるのは、インシデント対応フローを自動化したいということ。 検出を高速化するために生成AIを活用している。 Amazon Bedrockは VPC エンドポイントを介してデータをプライベートに保てる。 LLMをカスタマイズしたり、組織内のさまざまな状況で独自のLLMを構築する必要がある場合には Amazon Sage Makerを使用できる。 ここにいる皆さんがよく知っているであろうGuardDuty は2000のTOP AWS カスタマーのうち90%以上が利用している。 GuardDutyはECSランタイムモニタリング、EC2ランタイムモニタリングをサポートし、さらに多くのデータソースを収集できるようになった。コン トロール プレーンのログを側面からでなく、リソース上のデータを実際に確認し、脅威インテリジェンスを使用した 機械学習 や、分析 ユースケース を使用したさまざまな手段も実行する。 Amazon Detectiveは馴染みのない方もいるかもしれないが、主にGuardDutyの検出結果を調査するのに役立つサービス。 Amazon Detectiveはバックエンドにグラフデータベースを利用しており、リソースと環境間の関係を作成する。 MITRE ATT&CKのような攻撃 フレームワーク に基づいて調査結果をグループ化し、要約を表示する機能をリリースした( Amazon Detective finding group summaries )。我々はこの機能をいち早く提供したかった。 Amazon Inspectorはソフトウェアの 脆弱性 管理にフォーカスしたサービス。 発表されたLambdaコードスキャンでは、コードの 脆弱性 を見つけ、修正方針、もしくは修正方針を盛り込んだ新たなコードが表示されるようになった。 AWS GenAI Chatbot を使用することで、例えば現在のsecurity findingsに適用するための自環境でのIAM戦略であったり、セキュリティ対策の優先度を教えてくれる。 Amazon Qはマネージドサービスであり、カスタマイズができない。インシデントレスポンスでは AWS GenAI Chatbotが選択肢となる。 最後にしっかりre:Inforceを宣伝する。 感想 今回、セキュリティ系サービスの中でも特にDetectiveのアップデートは多かったと思います。 ただし、Detectiveを有効活用している事例をあまり聞かないので、まだ浸透はしておらず、これから来るんだろうなといった印象を受けました。 また、インシデント対応における生成AIの活用については、発表のあった Amazon Qではなく、 AWS GenAI Chatbotを活用するという点は発見でした。初見だったため一度試してみようかなと思います。 さて、今回参加してみたChalk Talk ですが、スピーカーが話している最中に、質問がバンバン飛び交うようなセッションは、日本ではあまり体験できないんじゃないかと思い、刺激を受けました。あのバイタリティを見習いたいものです。 現地に行かれる場合はぜひ参加してみるといいと思います! 最後に 今年は生成AIが大きなテーマとなった印象を受け、セキュリティにも生成AI活用の波がきていることを身をもって感じました。 また、同じセキュリティ関連のセッションに、業務でセキュリティに携わっている日本人の方も参加されており、セッション後にコミュニケーションを取ることができたことも収穫の1つでした。 来年も現地に行けるといいなあ〜。と余韻に浸りながら、今年の締め括りにしたいと思います。 最後まで読んでいただき、ありがとうございました! 執筆: @fukuyama.kenta 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
どーもー!X イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの福山です。 本記事は 電通国際情報サービス Advent Calendar 2023 6日目の記事となります。 念願だった AWS re:Invent現地参戦、ついに実現しました! 本レポートでは、セキュリティ関連のセッションに絞って紹介したいと思います。 re:Inventとは セッション内容 Builders' Session セッション名:Patch it up: Building a vulnerability management solution 感想 Chalk Talk セッション名:How to automate incident response with AWS security services & AI/ML 感想 最後に re:Inventとは 年に一度米国ラスベガスで開催される、 AWS 主催のIT業界最大規模のイベントです。 今年は日本から1000人以上、全体の参加者は約4〜5万人とのことで、規模の大きさがわかるかと思います。 re:Invent自体は2000以上のセッションが用意されているのと、 AWS やスポンサーと交流できるEXPOが主な内容です。 セッション内容 Builders' Session Builders' Sessionとは、少人数のグループで実際に手を動かしながら学んでいくタイプのセッションで、所要時間は1時間程度です。 各テーブルに AWS エキスパートがついており、気軽に質問できるのが特徴です。 参加したBuilders' Sessionのうち、1つ紹介します。 セッション名:Patch it up: Building a vulnerability management solution セッション概要: With thousands of new common vulnerabilities and exposures discovered every month, organizations can find it challenging to keep on top of their security using traditional, on-premises patching solutions. And the shift to containers and serverless can seem complex in an already diverse IT environment. Join this builders’ session to take a deep dive into patch management across a hybrid landscape with scenarios played out for containers, serverless, and virtual machines using AWS services. You must bring your laptop to participate. 複数タイプのリソースを 脆弱性 管理する方法を学ぶ。 Cloud9上でCDKの実行環境を構築して実行し、下図のような 脆弱性 管理の仕組みを構築していく。 ここでの 脆弱性 管理というのは、パッケージの 脆弱性 、コードの 脆弱性 、ネットワーク構成の 脆弱性 を指している。 リソース別に、EC2、ECR、Lambda、ネットワークは Amazon Inspectorで検出し、オンプレサーバは AWS Systems Manager Patch Managerで検出する。それらのFindingsをSecurity Hubに集約する。 Amazon Inspectorは検知までとなるが、 AWS Systems Manager Patch Managerは検知後のパッチ適用まで自動化することも可能。 感想 複数種類のリソースの 脆弱性 管理をするのは大変ですが、オンプレサーバ+ AWS リソースに限れば、Inspector、Patch Manager、Security Hubでここまでカバーできるんだと知りました。 しかもこれらをCloud9で構築できるから素晴らしいですね。 なお、Patch Mangerによるパッチ適用の自動化に関しては、いきなり本番で実行するのではなく、検証環境を用意して一度試してみた方がよさそうです。 あとは思いつき(要望)ですが、ソフトウェア名とバージョンを手動入力すると CPE (Common Platform Enumeration)が登録され、Inspector等でスキャンができるような機能があれば、管理の幅がさらに広がって最高なんだけどなぁ〜と思ったりしました。 さて、今回参加してみたBuilders' Sessionですが、それとは別で予約がすぐ埋まる人気セッションタイプの一つにWorkshopというものがあります。 WorkshopはBuilders' Sessionの長時間コースという位置付けになります。 Builder's SessionやWorkshopは現地でしか体験できないものなので、ぜひやっておくべきものだと思います。 Chalk Talk Chalk Talk は対話型のセッションです。後日オンラインで視聴できるBreakout Session同様、基本は AWS 側で講演を進めていきますが、参加者側から任意のタイミングで質問できます。そのため、Breakout Sessionより参加人数が制限されています。また、オンライン視聴では参加者からの質問が音声に含まれませんので、全て聞くには現地に参加するしかありません。 参加したChalk Talk のうち、1つを紹介します。 セッション名:How to automate incident response with AWS security services & AI/ML セッション概要: In this chalk talk , explore how generative AI and machine learning can be used alongside key AWS security services to enrich security analytics and automate incident response for security teams, helping to reduce the time to respond to security incidents and improve the developer experience. 顧客から頻繁に話題にあがるのは、インシデント対応フローを自動化したいということ。 検出を高速化するために生成AIを活用している。 Amazon Bedrockは VPC エンドポイントを介してデータをプライベートに保てる。 LLMをカスタマイズしたり、組織内のさまざまな状況で独自のLLMを構築する必要がある場合には Amazon Sage Makerを使用できる。 ここにいる皆さんがよく知っているであろうGuardDuty は2000のTOP AWS カスタマーのうち90%以上が利用している。 GuardDutyはECSランタイムモニタリング、EC2ランタイムモニタリングをサポートし、さらに多くのデータソースを収集できるようになった。コン トロール プレーンのログを側面からでなく、リソース上のデータを実際に確認し、脅威インテリジェンスを使用した 機械学習 や、分析 ユースケース を使用したさまざまな手段も実行する。 Amazon Detectiveは馴染みのない方もいるかもしれないが、主にGuardDutyの検出結果を調査するのに役立つサービス。 Amazon Detectiveはバックエンドにグラフデータベースを利用しており、リソースと環境間の関係を作成する。 MITRE ATT&CKのような攻撃 フレームワーク に基づいて調査結果をグループ化し、要約を表示する機能をリリースした( Amazon Detective finding group summaries )。我々はこの機能をいち早く提供したかった。 Amazon Inspectorはソフトウェアの 脆弱性 管理にフォーカスしたサービス。 発表されたLambdaコードスキャンでは、コードの 脆弱性 を見つけ、修正方針、もしくは修正方針を盛り込んだ新たなコードが表示されるようになった。 AWS GenAI Chatbot を使用することで、例えば現在のsecurity findingsに適用するための自環境でのIAM戦略であったり、セキュリティ対策の優先度を教えてくれる。 Amazon Qはマネージドサービスであり、カスタマイズができない。インシデントレスポンスでは AWS GenAI Chatbotが選択肢となる。 最後にしっかりre:Inforceを宣伝する。 感想 今回、セキュリティ系サービスの中でも特にDetectiveのアップデートは多かったと思います。 ただし、Detectiveを有効活用している事例をあまり聞かないので、まだ浸透はしておらず、これから来るんだろうなといった印象を受けました。 また、インシデント対応における生成AIの活用については、発表のあった Amazon Qではなく、 AWS GenAI Chatbotを活用するという点は発見でした。初見だったため一度試してみようかなと思います。 さて、今回参加してみたChalk Talk ですが、スピーカーが話している最中に、質問がバンバン飛び交うようなセッションは、日本ではあまり体験できないんじゃないかと思い、刺激を受けました。あのバイタリティを見習いたいものです。 現地に行かれる場合はぜひ参加してみるといいと思います! 最後に 今年は生成AIが大きなテーマとなった印象を受け、セキュリティにも生成AI活用の波がきていることを身をもって感じました。 また、同じセキュリティ関連のセッションに、業務でセキュリティに携わっている日本人の方も参加されており、セッション後にコミュニケーションを取ることができたことも収穫の1つでした。 来年も現地に行けるといいなあ〜。と余韻に浸りながら、今年の締め括りにしたいと思います。 最後まで読んでいただき、ありがとうございました! 執筆: @fukuyama.kenta 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 クラウド イノベーション センターの柴田です。 この記事は 電通国際情報サービス Advent Calendar 2023 の5日目の投稿です。 前日の記事は宮澤さんの「Jira Automationで作成した変数のスコープについて」でした。 はじめに 入力変数の検証 概要 設定方法 検証 オブジェクトの事前条件と事後条件の検証 概要 ユースケース 設定方法 リソース、データソース 出力 検証 構築したインフラストラクチャの check による検証 概要 ユースケース 設定方法 検証 まとめ おわりに 参考 はじめに Terraformでインフラスト ラク チャを構築する際、変数やリソースが期待する条件を満たしているか検証したいケースがあると思います。 この記事では変数やオブジェクトの検証に役立つTerraformの以下の機能を紹介します。 入力変数の検証 オブジェクトの事前条件と事後条件の検証 構築したインフラスト ラク チャの check による検証 なお、この記事の内容は以下のバージョンのTerraformを前提とします。 $ terraform version Terraform v1.6.3 on linux_amd64 入力変数の検証 概要 入力変数の値が指定した条件を満たしているか validation を用いて検証します。 この機能はTerraform v0.13.0以降で利用できます。 設定方法 variable に1つ以上の validation を設定します。 中身は以下の表のとおりです。 入力変数の検証の場合 condition が参照できる変数は自身のみです。 項目 説明 condition 満たすべき条件。 true なら検証成功、 false なら検証失敗。 error_message 検証に失敗した場合に表示されるエラーメッセージ。 variable "image_id" { type = string description = "The id of the machine image (AMI) to use for the server." validation { condition = can ( regex ( "^ami-" , var.image_id)) error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"." } } 検証 planとapplyの実行時に各 variable の condition が評価されます。 condition が false になるとTerraformは error_message を含むエラーを表示して異常終了します。 $ terraform plan -var image_id=123 Planning failed. Terraform encountered an error while generating this plan. ╷ │ Error: Invalid value for variable │ │ on variables.tf line 1: │ 1: variable "image_id" { │ ├──────────────── │ │ var.image_id is "123" │ │ The image_id value must be a valid AMI id, starting with "ami-". │ │ This was checked by the validation rule at variables.tf:5,3-13. ╵ オブジェクトの事前条件と事後条件の検証 概要 各オブジェクトを評価する前後に、指定した条件を満たしているかを検証します。 事前条件は precondition 、事後条件は postcondition を使用します。 この機能はTerraform v1.2.0以降で利用できます。 ユースケース 事前条件には特定のオブジェクトを評価するために満たすべき前提条件を記述します。 事後条件には特定のオブジェクトが評価後に保証すべき条件を記述します。 設定方法 以下のオブジェクトに事前条件と事後条件を設定できます。 リソース( resource ) データソース( data ) 出力( output ) ※事前条件のみ利用可能 リソース、データソース resource または data の lifecycle に事前条件 precondition または事後条件 postcondition を設定します。 それぞれ中身は先ほどと同じです。 入力変数の検証と異なり condition は他のオブジェクトを参照できます。 resource "aws_instance" "example" { instance_type = "t3.micro" ami = data.aws_ami.example.id lifecycle { precondition { condition = data.aws_ami.example.architecture == "x86_64" error_message = "The selected AMI must be for the x86_64 architecture." } postcondition { condition = self.public_dns != "" error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled." } } } 上の例ではリソース aws_instance.example に対して以下の条件を設定しています。 リソースを作成するためにAMIの アーキテクチャ は x86_64 でなければなりません。 リソースの作成後にパブリック DNS が設定されていることを保証します。 self は評価中のオブジェクト自身を参照するオブジェクトです。事後条件でのみ利用できます。 事前条件と事後条件は count や for_each と併用できます。 出力 output に事前条件 precondition を設定します。 事後条件 postcondition は設定できません。 中身は先ほどと同じです。 output "ami_id" { value = data.aws_ami.example.id precondition { condition = data.aws_ami.example.architecture == "x86_64" error_message = "The selected AMI must be for the x86_64 architecture." } } 検証 planとapplyの実行時に各オブジェクトの事前条件 precondition と事後条件 postcondition の condition が評価されます。 各オブジェクトの検証のライフサイクルは以下のとおりです。 事前条件の検証 オブジェクトの評価 事後条件の検証 事後条件は「apply後に評価される条件」ではなく「オブジェクトの評価後に評価される条件」です。 そのためplanの実行時にも事後条件は評価されます。 condition に未確定の値が含まれる場合、その condition の評価は値が確定するまで保留されます。 特に known after apply な値を含む condition はplanの実行時には評価されません。 condition が false になると、以降の処理は中断され、Terraformは error_message を含むエラーを表示して異常終了します。 ただし作成済みのリソースは削除されません。 $ terraform plan data.aws_ami.example: Reading... data.aws_ami.example: Read complete after 0s [id=ami-0d8d9f072b1e8e8fe] Planning failed. Terraform encountered an error while generating this plan. ╷ │ Error: Resource precondition failed │ │ on condition.tf line 17, in resource "aws_instance" "example": │ 17: condition = data.aws_ami.example.architecture == "x86_64" │ ├──────────────── │ │ data.aws_ami.example.architecture is "arm64" │ │ The selected AMI must be for the x86_64 architecture. ╵ 構築したインフラスト ラク チャの check による検証 概要 planとapplyの実行の終わりに指定した条件が満たされているか check を用いて検証します。 check の検証は、これまで説明した他の検証機能と異なり、検証に失敗してもplanやapplyの実行は中断されません。 この機能はTerraform v1.5.0以降で利用できます。 ユースケース 構築したインフラスト ラク チャが指定した条件を満たしているか検証します。 check は事後条件と似ていますが 特定のオブジェクトではなくインフラスト ラク チャ全体を検証したい場合 検証に失敗した際にエラーを発生させて処理を中断したくない場合 には事後条件よりも check を使うとよいでしょう。 設定方法 0〜1個のデータソース 1個以上の assert を含む check を設定します。 check 内のデータソースはスコープ付きデータソースと呼ばれ、以下の特徴があります。 check の外側からは参照できません。 for_each や count との併用はできません。 assert の中身は先ほどと同じです。 check "health_check" { data "http" "alb" { url = "https://$ { aws_lb.example.dns_name } " } assert { condition = data.http.alb.status_code == 200 error_message = "$ { data.http.alb.url } returned an unhealthy status code" } } 検証 planとapplyの実行の終わりに condition が評価されます。 事前条件や事後条件と同じく known after apply な値に依存する condition はplanの実行時には評価されません。 インフラスト ラク チャがまだ構築されていないplan時に check を評価したくない場合は、リソースが実際に作成された後にスコープ付きデータソースおよび condition が評価されるよう、スコープ付きデータソースからリソースへの依存関係を depends_on などを使って設定するとよいでしょう。 condition が false になった場合、またはスコープ付きデータソースのproviderでエラーが発生した場合、Terraformは error_message を含む警告を表示して処理を継続します。 $ terraform plan # (中略) ╷ │ Warning: Error making request │ │ with data.http.alb, │ on main.tf line 14, in check "health_check": │ 14: data "http" "alb" { │ │ Error making request: GET https://example.com.invalid giving up after 1 attempt(s): Get "https://example.com.invalid": dial tcp: lookup example.com.invalid on 127.0.0.53:53: no such host ╵ まとめ 以下の表はここまでの内容をまとめたものです。 主な ユースケース 記述箇所 検証されるタイミング count , for_each の併用 condition が false になった場合の挙動 入力変数の検証 入力変数の検証 variable variable の評価前 不可 異常終了 オブジェクトの事前条件の検証 特定のオブジェクトを評価するために満たすべき前提条件の検証 resource , data , output 各オブジェクトの評価前 可 異常終了。作成済みのリソースは削除されない。 オブジェクトの事後条件の検証 特定のオブジェクトが評価後に保証すべき条件の検証 resource , data 各オブジェクトの評価後 可 異常終了。作成済みのリソースは削除されない。 構築したインフラスト ラク チャの check による検証 構築したインフラスト ラク チャの検証 check (スコープ付きデータソースを利用) planとapplyの終わり 不可 警告を表示して処理を継続する おわりに この記事ではTerraformの変数やオブジェクトが期待する条件を満たしているか検証する方法として以下の機能を紹介しました。 入力変数の検証 オブジェクトの事前条件と事後条件の検証 構築したインフラスト ラク チャの check による検証 他にもTerraform v1.6で Tests の機能が導入されるなど、最近はTerraformの検証・テストに関する機能がどんどん充実していると感じます。 これらの機能を活用してより安全にインフラスト ラク チャを構築したいですね。 ここまで読んでいただきありがとうございました。 参考 Custom Conditions - Configuration Language | Terraform | HashiCorp Developer Checks - Configuration Language | Terraform | HashiCorp Developer 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @shibata.takao 、レビュー: @fukutake.hiroaki ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 クラウド イノベーション センターの柴田です。 この記事は 電通国際情報サービス Advent Calendar 2023 の5日目の投稿です。 前日の記事は宮澤さんの「Jira Automationで作成した変数のスコープについて」でした。 はじめに 入力変数の検証 概要 設定方法 検証 オブジェクトの事前条件と事後条件の検証 概要 ユースケース 設定方法 リソース、データソース 出力 検証 構築したインフラストラクチャの check による検証 概要 ユースケース 設定方法 検証 まとめ おわりに 参考 はじめに Terraformでインフラスト ラク チャを構築する際、変数やリソースが期待する条件を満たしているか検証したいケースがあると思います。 この記事では変数やオブジェクトの検証に役立つTerraformの以下の機能を紹介します。 入力変数の検証 オブジェクトの事前条件と事後条件の検証 構築したインフラスト ラク チャの check による検証 なお、この記事の内容は以下のバージョンのTerraformを前提とします。 $ terraform version Terraform v1.6.3 on linux_amd64 入力変数の検証 概要 入力変数の値が指定した条件を満たしているか validation を用いて検証します。 この機能はTerraform v0.13.0以降で利用できます。 設定方法 variable に1つ以上の validation を設定します。 中身は以下の表のとおりです。 入力変数の検証の場合 condition が参照できる変数は自身のみです。 項目 説明 condition 満たすべき条件。 true なら検証成功、 false なら検証失敗。 error_message 検証に失敗した場合に表示されるエラーメッセージ。 variable "image_id" { type = string description = "The id of the machine image (AMI) to use for the server." validation { condition = can ( regex ( "^ami-" , var.image_id)) error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"." } } 検証 planとapplyの実行時に各 variable の condition が評価されます。 condition が false になるとTerraformは error_message を含むエラーを表示して異常終了します。 $ terraform plan -var image_id=123 Planning failed. Terraform encountered an error while generating this plan. ╷ │ Error: Invalid value for variable │ │ on variables.tf line 1: │ 1: variable "image_id" { │ ├──────────────── │ │ var.image_id is "123" │ │ The image_id value must be a valid AMI id, starting with "ami-". │ │ This was checked by the validation rule at variables.tf:5,3-13. ╵ オブジェクトの事前条件と事後条件の検証 概要 各オブジェクトを評価する前後に、指定した条件を満たしているかを検証します。 事前条件は precondition 、事後条件は postcondition を使用します。 この機能はTerraform v1.2.0以降で利用できます。 ユースケース 事前条件には特定のオブジェクトを評価するために満たすべき前提条件を記述します。 事後条件には特定のオブジェクトが評価後に保証すべき条件を記述します。 設定方法 以下のオブジェクトに事前条件と事後条件を設定できます。 リソース( resource ) データソース( data ) 出力( output ) ※事前条件のみ利用可能 リソース、データソース resource または data の lifecycle に事前条件 precondition または事後条件 postcondition を設定します。 それぞれ中身は先ほどと同じです。 入力変数の検証と異なり condition は他のオブジェクトを参照できます。 resource "aws_instance" "example" { instance_type = "t3.micro" ami = data.aws_ami.example.id lifecycle { precondition { condition = data.aws_ami.example.architecture == "x86_64" error_message = "The selected AMI must be for the x86_64 architecture." } postcondition { condition = self.public_dns != "" error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled." } } } 上の例ではリソース aws_instance.example に対して以下の条件を設定しています。 リソースを作成するためにAMIの アーキテクチャ は x86_64 でなければなりません。 リソースの作成後にパブリック DNS が設定されていることを保証します。 self は評価中のオブジェクト自身を参照するオブジェクトです。事後条件でのみ利用できます。 事前条件と事後条件は count や for_each と併用できます。 出力 output に事前条件 precondition を設定します。 事後条件 postcondition は設定できません。 中身は先ほどと同じです。 output "ami_id" { value = data.aws_ami.example.id precondition { condition = data.aws_ami.example.architecture == "x86_64" error_message = "The selected AMI must be for the x86_64 architecture." } } 検証 planとapplyの実行時に各オブジェクトの事前条件 precondition と事後条件 postcondition の condition が評価されます。 各オブジェクトの検証のライフサイクルは以下のとおりです。 事前条件の検証 オブジェクトの評価 事後条件の検証 事後条件は「apply後に評価される条件」ではなく「オブジェクトの評価後に評価される条件」です。 そのためplanの実行時にも事後条件は評価されます。 condition に未確定の値が含まれる場合、その condition の評価は値が確定するまで保留されます。 特に known after apply な値を含む condition はplanの実行時には評価されません。 condition が false になると、以降の処理は中断され、Terraformは error_message を含むエラーを表示して異常終了します。 ただし作成済みのリソースは削除されません。 $ terraform plan data.aws_ami.example: Reading... data.aws_ami.example: Read complete after 0s [id=ami-0d8d9f072b1e8e8fe] Planning failed. Terraform encountered an error while generating this plan. ╷ │ Error: Resource precondition failed │ │ on condition.tf line 17, in resource "aws_instance" "example": │ 17: condition = data.aws_ami.example.architecture == "x86_64" │ ├──────────────── │ │ data.aws_ami.example.architecture is "arm64" │ │ The selected AMI must be for the x86_64 architecture. ╵ 構築したインフラスト ラク チャの check による検証 概要 planとapplyの実行の終わりに指定した条件が満たされているか check を用いて検証します。 check の検証は、これまで説明した他の検証機能と異なり、検証に失敗してもplanやapplyの実行は中断されません。 この機能はTerraform v1.5.0以降で利用できます。 ユースケース 構築したインフラスト ラク チャが指定した条件を満たしているか検証します。 check は事後条件と似ていますが 特定のオブジェクトではなくインフラスト ラク チャ全体を検証したい場合 検証に失敗した際にエラーを発生させて処理を中断したくない場合 には事後条件よりも check を使うとよいでしょう。 設定方法 0〜1個のデータソース 1個以上の assert を含む check を設定します。 check 内のデータソースはスコープ付きデータソースと呼ばれ、以下の特徴があります。 check の外側からは参照できません。 for_each や count との併用はできません。 assert の中身は先ほどと同じです。 check "health_check" { data "http" "alb" { url = "https://$ { aws_lb.example.dns_name } " } assert { condition = data.http.alb.status_code == 200 error_message = "$ { data.http.alb.url } returned an unhealthy status code" } } 検証 planとapplyの実行の終わりに condition が評価されます。 事前条件や事後条件と同じく known after apply な値に依存する condition はplanの実行時には評価されません。 インフラスト ラク チャがまだ構築されていないplan時に check を評価したくない場合は、リソースが実際に作成された後にスコープ付きデータソースおよび condition が評価されるよう、スコープ付きデータソースからリソースへの依存関係を depends_on などを使って設定するとよいでしょう。 condition が false になった場合、またはスコープ付きデータソースのproviderでエラーが発生した場合、Terraformは error_message を含む警告を表示して処理を継続します。 $ terraform plan # (中略) ╷ │ Warning: Error making request │ │ with data.http.alb, │ on main.tf line 14, in check "health_check": │ 14: data "http" "alb" { │ │ Error making request: GET https://example.com.invalid giving up after 1 attempt(s): Get "https://example.com.invalid": dial tcp: lookup example.com.invalid on 127.0.0.53:53: no such host ╵ まとめ 以下の表はここまでの内容をまとめたものです。 主な ユースケース 記述箇所 検証されるタイミング count , for_each の併用 condition が false になった場合の挙動 入力変数の検証 入力変数の検証 variable variable の評価前 不可 異常終了 オブジェクトの事前条件の検証 特定のオブジェクトを評価するために満たすべき前提条件の検証 resource , data , output 各オブジェクトの評価前 可 異常終了。作成済みのリソースは削除されない。 オブジェクトの事後条件の検証 特定のオブジェクトが評価後に保証すべき条件の検証 resource , data 各オブジェクトの評価後 可 異常終了。作成済みのリソースは削除されない。 構築したインフラスト ラク チャの check による検証 構築したインフラスト ラク チャの検証 check (スコープ付きデータソースを利用) planとapplyの終わり 不可 警告を表示して処理を継続する おわりに この記事ではTerraformの変数やオブジェクトが期待する条件を満たしているか検証する方法として以下の機能を紹介しました。 入力変数の検証 オブジェクトの事前条件と事後条件の検証 構築したインフラスト ラク チャの check による検証 他にもTerraform v1.6で Tests の機能が導入されるなど、最近はTerraformの検証・テストに関する機能がどんどん充実していると感じます。 これらの機能を活用してより安全にインフラスト ラク チャを構築したいですね。 ここまで読んでいただきありがとうございました。 参考 Custom Conditions - Configuration Language | Terraform | HashiCorp Developer Checks - Configuration Language | Terraform | HashiCorp Developer 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @shibata.takao 、レビュー: @fukutake.hiroaki ( Shodo で執筆されました )
アバター