ISID テックブログ

ISIDが運営する技術ブログ

開発環境のAurora Serverless費用節約術(CDKサンプルコード付き)

こんにちは。X(クロス)イノベーション本部の耿です。

Amazon Aurora Serverlessは、コンピューティングとメモリのキャパシティ(Aurora 容量ユニット = ACU)をリクエスト数に応じて自動で変化させることができるデータベースサービスです。事前のスケーリング計画が不要になるほか、実際のワークロードに合わせてキャパシティを増減させるため、費用の最適化に向いています。

Aurora Serverlessは v1 と v2 の2つのバージョンが一般利用可能ですが、v1 の方はしばらくアクセスがないと 0 ACU まで落として一時停止する機能があり、コンピューティングとメモリの料金が全くかからなくなります。本番環境であれば利用されることはあまりないと思いますが、リクエスト数が限定的な開発環境ではこの機能をうまく利用することで、費用をさらに節約することができます。

しかし、 0 ACU で一時停止している状態のデータベースにアクセスしようとすると、キャパシティがないため初回接続が失敗してしまいます。本記事はシンプルにこの問題を解消し、費用を抑える方法を記載します。

Aurora Serverless の ACU 設定

Aurora Serverless は Aurora 容量ユニット (ACU) の最大値と最小値を指定することで、その間で負荷に応じてオートスケーリングします。v1に限って一時停止設定が可能で、リクエストが全くない時に 0 ACU までスケールダウンできます。

Aurora Serverless v1の一時停止設定 Aurora Serverless v1の一時停止設定

Aurora Serverless のバージョンの違い

2022年5月時点において、Aurora Serverless v1にできてv2にできないことがあります。

  • v2はData APIを利用できない
  • CloudFormationはまだv2をサポートしていない
  • v2は 0 ACU(一時停止)にできず、最小ACUは 0.5 である

ACUに関する補足として、2022年5月時点でv2のACU費用はv1の2倍に設定されています。すなわちv2を最小キャパシティ0.5 ACUで常時稼働した場合の費用は、v1を1 ACUで常時稼働した場合の費用と同等になります。v1は一時停止が可能なため、一時停止をした時間の分だけv2より費用を節約できます。

※その他のAurora Serverless v2の特徴は、以下の動画で詳しく解説されています。
JAWS-UG横浜 #44 Aurora Serverless v2

開発で遭遇した問題点

Data APIとCDKを利用したかったため、Aurora Serverless v1を利用して開発している環境があります。費用を抑えるために一時停止機能を有効にしていますが、0 ACUのときはコンピューティングとメモリの動作が完全に停止するため、初回接続ではデータベースへの接続が失敗するという問題に遭遇しました。

Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.; SQLState: 08S01

しばらく(体感30秒〜1分程度)するとデータベースが起動し、問題なく接続できるようになるのですが、費用を抑えつつ初回接続でも失敗しないようにしたかったため、その仕組みを検討しました。

実現したいこと

Aurora Serverlessにアクセスする可能性のある時間帯は一時停止をさせずに最低でも1 ACUで稼働させ、それ以外の時間帯は0 ACUへのスケールダウンを許容すれば、余分な費用を削減し、初回接続でエラーになる問題も解消できます。今回はアクセスする可能性のある時間帯は余裕を持って 7:00 ~ 22:00 と広めに定義しています。
整理すると、実現したいことは次のようになります。

  • 平日の7:00 ~ 22:00 は、Aurora Serverlessが 0 ACUにならないようしに、DBへの接続が常に成功するようにしたい
  • それ以外の時間(平日深夜と土日)は 0 ACUにスケールダウンしても良い

実現方法

  • Aurora クラスタが一時停止しない場合の最小 ACU を 1 とする
  • Aurora クラスタ2時間アクセスがなかった場合、一時停止するよう(0 ACU になるよう)に設定する
  • Aurora クラスタへ参照系のクエリを発行するLambda関数を作成し、平日の7:00 ~ 20:00 の間は1時間ごとに定期実行する

こうすることで、平日は20:00に最後のLambda関数が実行され、22:00まではAuroraクラスタが0 ACUにならないことが保証されます。夜間や土日はAuroraクラスタへの接続がないと一時停止し、次の平日の朝7:00に最初のLambda関数が実行され、Auroraクラスタが起動します。

システム構成図

他の選択肢

シビアに費用を抑える必要がない場合、一時停止設定を無効にし、0 ACUにスケールダウンしないようにすれば、データベースへのアクセスがなくても停止しなくなり、本記事で述べるようなワークアラウンドは必要ありません。

あるいは 0 ACUにスケールダウンした後の初回接続が失敗しても、起動を待ってからクエリを再実行することが許容できる場合も、本記事のワークアラウンドは必要ありません。

CDKコード

以下のCDKコードで環境を作成します。

import { Duration, Stack, StackProps } from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as events from "aws-cdk-lib/aws-events";
import * as eventTargets from "aws-cdk-lib/aws-events-targets";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as rds from "aws-cdk-lib/aws-rds";
import { Construct } from "constructs";

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const privateSubnetName = "myPrivateSubnet";

    // VPCを作成
    const vpc = new ec2.Vpc(this, "MyVpc", {
      cidr: "10.0.0.0/16",
      enableDnsHostnames: true,
      enableDnsSupport: true,
      subnetConfiguration: [
        {
          name: privateSubnetName,
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
          cidrMask: 20,
        },
      ],
    });

    // Aurora Serverless v1 クラスタを作成
    const auroraCluseter = new rds.ServerlessCluster(this, "MyAuroraCluster", {
      engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
      vpc: vpc,
      vpcSubnets: { subnetGroupName: privateSubnetName },
      // 最小ACUを1、最大ACUを2、一時停止までに必要な非アクティブ時間を2時間に指定
      scaling: { minCapacity: 1, maxCapacity: 2, autoPause: Duration.hours(2) },
      // Data APIを有効化
      enableDataApi: true,
    });

    // Lambda関数
    const awakeAuroraFunction = new lambdaNodejs.NodejsFunction(this, "MyFunction", {
      // Lambda関数へのファイルパス
      entry: "functions/awake-aurora-serverless.ts",
      runtime: lambda.Runtime.NODEJS_14_X,
      // Data APIを利用するため、AuroraクラスタARNとシークレットのARNを環境変数で渡す
      environment: {
        CLUSTER_ARN: auroraCluseter.clusterArn,
        SECRET_ARN: auroraCluseter.secret?.secretArn ?? "",
      },
    });

    // Lambda関数の実行ロールにData APIへのアクセスを許可するポリシーを追加
    if (auroraCluseter.secret?.secretArn) {
      const auroraDataApiPolicy = new iam.ManagedPolicy(this, "MyAuroraDataApiPolicy", {
        statements: [
          new iam.PolicyStatement({
            resources: [`${auroraCluseter.secret.secretArn}*`],
            actions: ["secretsmanager:GetSecretValue"],
            effect: iam.Effect.ALLOW,
          }),
          new iam.PolicyStatement({
            resources: [auroraCluseter.clusterArn],
            actions: ["rds-data:ExecuteStatement"],
            effect: iam.Effect.ALLOW,
          }),
        ],
      });
      awakeAuroraFunction.role?.addManagedPolicy(auroraDataApiPolicy);
    }

    // 日本時間 平日の 7:00 ~ 20:00 の間に1時間ごとにLambda関数を定期起動する
    // cron式は UTC で記載するため、「日曜 ~ 木曜の22:00 ~ 23:00」と「月曜 ~ 金曜の 0:00 ~ 11:00」の2つに分ける
    new events.Rule(this, "MyEventRule1", {
      schedule: events.Schedule.cron({ minute: "0", hour: "22-23", weekDay: "SUN-THU" }),
      targets: [new eventTargets.LambdaFunction(awakeAuroraFunction, { retryAttempts: 3 })],
    });
    new events.Rule(this, "MyEventRule2", {
      schedule: events.Schedule.cron({ minute: "0", hour: "0-11", weekDay: "MON-FRI" }),
      targets: [new eventTargets.LambdaFunction(awakeAuroraFunction, { retryAttempts: 3 })],
    });
  }
}

Lambda関数の部分では、 NodejsFunction コンストラクトを利用することで、TypeScript で記述した Lambda 関数の JavaScriptへのコンパイルからデプロイまでをCDKがやってくれます。

Auroraクラスタへクエリを発行するLambda関数は、 functions/awake-aurora-serverless.ts に以下のように実装します。Data APIを利用してクエリを発行します。

import * as RDS from "@aws-sdk/client-rds-data";
import { Handler } from "aws-lambda";

const client = new RDS.RDSDataClient({ region: "ap-northeast-1" });

export const handler: Handler = async () => {
  const input = {
    // Data APIを利用するための、AuroraクラスタのリソースARNとシークレットのARNを環境変数から取得する
    resourceArn: process.env.CLUSTER_ARN,
    secretArn: process.env.SECRET_ARN,
    // 発行するSQL文
    sql: "SHOW databases;",
  };
  const command = new RDS.ExecuteStatementCommand(input);
  await client.send(command);
};

発行するSQL文は更新系でなければ何でも良いですが、シンプルに SHOW databases; としています。

実際の料金

以上の構成でしばらく稼働した場合の料金を確認しました。

利用料金のグラフ

土日はAuroraクラスタが停止し、料金がかかっていないことがわかります。平日も24時間のうち15時間しか稼働していません。
今回の条件では一週間 24h * 7日 = 168h のうち、 15h * 5日 = 75h 稼働しているため、常時稼働に比べて 75h / 168h = 45% の費用に抑えることができています。
一方で平日の業務時間中は常に稼働状態のため、接続が失敗することがなくなり、今回の要件を満たすことができました。

執筆:@kou.kinyo2、レビュー:@higaShodoで執筆されました

Fluent Bitを利用したログルーティング - 入門編

こんにちは、電通国際情報サービス デジタルイノベーション1部の加世です。

今回は「FluentBitを利用したログルーティング」を進める際に、「FluentBitについて理解する」ことを目的とした記事となっております。 具体的なFluentBitの使い所や設計を考える前段階として、本記事を参考にしていただければと思います。

Fluent Bitについて

FluentBitは、データ(メトリクス、ログ、それ以外の生情報など)をInput情報として収集し、加工処理したうえで任意の転送先に転送します。 クラウドサービスプロバイダを利用するうえでは、既に「システム」から「サービス」に転送する仕組みを用意していることが多いため、ログルーティングについて考慮しなくても問題ないケースが多いです。

そのため、FluentBitの利用は「複数のシステム(サーバ/コンテナ)」から「様々なデータ」を「複数の宛先」に「加工」して「集約」する場合が有効であると考えています。

Fluent Bitの特徴

FluentBitは、大きく6種類「Input」->「Parse」->「Filter」->「Buffer」->「Router」->「Output」のPluginで構成されています。 また、「Output」プラグインは、主要なクラウドサービスプロバイダ(AWS, Azure, GCP, Datadog...etc)に対応しています。 そのため、データ転送先はアーキテクチャ・データ分析方法を踏まえて、プラグインの対応範囲はありますがさまざまなサービスを選択できます。

FluentBitは、次の点で優れていると考えています。

検証環境と処理フローについて

検証環境は、「AWS Fargate」で「テストアプリケーションコンテナ (Firelensログドライバ付き)」「FluentBitコンテナ」を稼働しています。 また、FluentBitは、ログルーティング先として複数の転送先「CloudWatch」「S3」を指定します。

※FluentBitコンテナを使用できる環境であれば、FluentBitの基礎的な機能は検証できます。 ※検証環境のホストOSカーネルパラメータ設定によっては、挙動が変わる場合はあります。 ※FireLensログドライバは、Fargate標準出力(stdout/stderr)をFluentBitにログルーティングするためだけに使用します。

  • AWS FargateでFirelensログドライバを利用して、標準出力をFluentBitに送信する方式について Firelensログドライバは、仕様を細かく確認していませんが「td-agent」の仕組みでFluentBitのInputプラグイン「Forward」を利用して標準出力を転送していると思われます。 そのため、タグは「<コンテナ名>-firelens」となるため、必要に応じて「rewrite_tags」などでタグを変更することで、Outputプラグインでのタグ条件指定がしやすくなります。

  • 処理フロー

    1. アプリケーション(stdout/stderr) -> Firelensログドライバ -> (forward:24224) -> サイドカーコンテナ(FluentBit) -> CloudWatch/S3
    2. アプリケーション(ローカルファイル) -> (tail:Volume共有マウント) -> サイドカーコンテナ(FluentBit) -> CloudWatch/S3

  • AWS専用のFluentBitコンテナ

    https://docs.fluentbit.io/manual/installation/getting-started-with-fluent-bit

    AWSに最適化されたFluentBitコンテナが提供されています。
    マニュアルにしたがって、サイドカーコンテナとして起動します。

    • aws-for-fluent-bit
      AWS Fargateの場合は、「Firelens」ログドライバと連携して「aws-for-fluent-bit」が一部設定を自動生成します。
      また、オプション設定をすることで追加のFluentBit設定ファイルを読み込むことも可能です。

    • IAMロール設定について 「aws-for-fluent-bit」コンテナは出力先となる「CloudWatch」「S3」などに対して操作権限が必要になります。 そのため、Fargateコンテナを前提とした場合は「ECSタスクロール」に設定しておく必要があります。

      なお、AWS以外から「CloudWatch」「S3」に転送する場合は、FluentBit向けのIAMロールを用意して「cloudwatch」「S3」プラグインでIAMロールを指定して使用できます。

Fluent Bit設計前の準備

データパイプラインの理解

FluentBitを設計するうえで、データパイプライン「Input」->「Parse」->「Filter」->「Buffer」->「Router」->「Output」を理解する必要があります。

  • Input

    https://docs.fluentbit.io/manual/pipeline/inputs

    「Forward(TCPリスナポート経由のメッセージ受信)」「tail(ローカルファイスシステム上のログファイルなどのtail)」などで「生データ」の入力を受け付けます。 他にもメトリクスデータを取得するプラグインも多数用意されています。

  • Parse

    https://docs.fluentbit.io/manual/pipeline/parsers

    Inputプラグインで受け取ったデータを「Parser」で処理することにより、「データ構造化」や「マルチライン処理」を行います。
    「Input」「Filter」プラグインで使用できます。

    • データ構造化 「生データ(log)」を「時間(time)」「レベル(level)」「メッセージ(message)」などのフィールドに分割します。

    • マルチライン処理
      複数行データ(たとえば、Javaスタックトレースの「at」など)を1つのデータとして扱います。
      ※なお、「\n」「\t」がエスケープされずにファイル出力されますが、回避方法は執筆時点で未調査です。

    • 処理前

      {
          "hostname": "ip-172-24-136-132.ap-northeast-1.compute.internal",
          "level": "EROROR",
          "message": "org.apache.catalina.startup.Catalina.start Server startup in 135176 ms",
          "thread": "main",
          "time": "02-Mar-2022 15:33:32.000"
      }
      {
          "hostname": "ip-172-24-136-132.ap-northeast-1.compute.internal",
          "level": "EROROR",
          "message": "\tat com.myproject.module.MyProject.badMethod(MyProject.java:22)",
          "thread": "main",
          "time": "02-Mar-2022 15:33:32.000"
      }
      {
          "hostname": "ip-172-24-136-132.ap-northeast-1.compute.internal",
          "level": "EROROR",
          "message": "\tat com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)",
          "thread": "main",
          "time": "02-Mar-2022 15:33:32.000"
      }
      
    • 処理後

      {
          "hostname": "ip-172-24-136-132.ap-northeast-1.compute.internal",
          "level": "EROROR",
          "message": "org.apache.catalina.startup.Catalina.start Server startup in 135176 ms\n\tat com.myproject.module.MyProject.badMethod(MyProject.java:22)\n\tat com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)",
          "thread": "main",
          "time": "02-Mar-2022 15:33:32.000"
      }
      
  • Filter

    https://docs.fluentbit.io/manual/pipeline/filters

    入力された「生データ」を「Filter」で加工(追加・変更・整形・削除 etc)します。
    「Parser」「Multiline(Parser)」「Record Modifier」「Rewrite Tag」「Lua」「Kubernetes」などがあります。
    ※「AWS Metadata」はEC2向けであり、Fargate向けのものではありません。(Fargate自体は、メタデータを参照するURLは提供されています)

  • Buffer

    https://docs.fluentbit.io/manual/concepts/buffering

    生データを保管する領域として、「メモリ」または「ファイルシステム(永続領域)」を選択できます。
    「Service」「Input」「Output」プラグインなどで定義します。
    なお、「処理間隔(メモリリフレッシュ、データ出力などのタイミング)」「各種バッファサイズ(ファイル初期読み込みサイズなど)」はチューニング要素となります。

    • 補足
      通常は、「メモリ」のみを選択する方針で問題ありません。
      生データの過去分までの情報(オフセットなど)を持っておきたい場合は、「ファイルシステム」を選択します。

      Fargateなどを前提としたエフェメラルストレージを利用する場合は、コンテナ内のメトリクス・ログデータなどは更新時に初期化されるため「メモリ」を採用する点は問題ありません。
      一方で、データ分析などで永続領域にあるデータを漏れなく・重複なく読み込む必要がある場合は、EFSと連携するなどの永続領域のデータを参照する構成が必要になります。

  • Router

    https://docs.fluentbit.io/manual/concepts/data-pipeline/router

    出力対象となるデータは、Input時点でデータとひもづけられたTag(または、Filterで書き換えられたTag)をもとに「識別」できるようにします。
    そのうえで、正しく条件を指定することで適切な出力先に出力します。
    ルーティング条件は、Outputプラグインの「Match(ワイルドカード指定のみ)」と「Match_regex」により一致条件を設定します。

  • Output

    https://docs.fluentbit.io/manual/pipeline/outputs

    クラウドサービスプロバイダ向けのストレージサービス」「ローカルログファイル」「FluentBitコンテナの標準出力」などに対して「データ」を出力します。

データの構造化について

Inputデータは、「データフィールド名の追加・加工」「データ値の追加・加工」が可能です。 InputデータがJSON形式であり、すでに構造化されているデータの場合は、そのまま「フィールド名(キー名)」「値」を利用することが多いと考えられます。 ※ただし、どのようなデータでも「Filter」プラグインにより追加の加工処理は可能です。

  • フィールドの追加・加工
    Inputデータは、「Filter」プラグインでデータフィールド名を追加・加工できます。

    Firelensログドライバ経由でInputした標準出力・標準エラー出力は、「container_id」「container_name」「log」「source」フィールドをInputします。
    これは既に構造化されたInputデータですが、例えば「log」「source」に対して「キ名ー(フィールド名)」を統一する目的で「message」「file_path」に変換できます。

    また、追加の加工処理としてコンテナ内の環境変数「$HOSTNAME」の情報をもとに、「hostname」フィールドを追加できます。

    • Inputデータ

      {
          "container_id": "3885a532543547978adf8d6e9bdf729b-2449111020",
          "container_name": "test-container",
          "log": "test message.",
          "source": "stdout"
      }
      
      {
          "container_id": "625a41f4c1a241f49e7308b9g62911cb-1475094415",
          "container_name": "test-container",
          "log": "java.io.IOException: listener timeout after waiting for [60000] ms",
          "source": "stderr"
      }
      
    • 加工済みデータ

      {
          "container_id": "3885a532543547978adf8d6e9bdf729b-2449111020",
          "container_name": "test-container",
          "file_path": "stdout",
          "hostname": "ip-172-24-100-100.ap-northeast-1.compute.internal",
          "message": "test message.",
      }
      
      {
          "container_id": "625a41f4c1a241f49e7308b9g62911cb-1475094415",
          "container_name": "test-container",
          "file_path": "stderr"
          "hostname": "ip-172-24-100-100.ap-northeast-1.compute.internal",
          "message": "java.io.IOException: listener timeout after waiting for [60000] ms",
      }
      
  • データ値の追加・加工
    Inputデータは、「Filter」プラグインでデータ値を追加・加工できます。

    Docker標準出力などであれば、すでに「Parser」プラグインとして用意されているパーサ「docker」を利用できます。 事前に用意されているParserで構造化が難しい場合は、独自にParserを定義できます。

    たとえば、以下に示す独自の「Parser」プラグイン定義は、Tomcatの標準出力ログを加工します。 これは、InputデータのログをRegexに指定した正規表現でパースして、「time」「level」「thread」「message」などのフィールド名にマッピングします。

    • 「Parser」プラグイン定義

        [PARSER]
            Name        tomcat
            Format      regex
            Regex       /^(?<time>\d{1,2}-\D{3}-\d{4} \d{1,2}:\d{1,2}:\d{1,2}.\d{3}) (?<level>[^\s]+) \[(?<thread>[^\]]*)\] (?<message>.*)/
            Time_Key    time
            Time_Format %d-%b-%Y %H:%M:%S.%L
            Time_Offset +0900
            Time_Keep   On
            # Command       | Decoder      | Field   | Optional Action   |
            # ==============|==============|=========|===================|
            Decode_Field_As   escaped        message
      
    • インプットデータ

        04-Mar-2022 12:34:39.072 ERROR [main] org.apache.catalina.startup.Catalina.start Server startup in 123094 ms
      
    • 加工済みデータ

        {
            "file_path": "/usr/local/tomcat/logs/catalina.test.log",
            "hostname": "ip-172-24-136-164.ap-northeast-1.compute.internal",
            "level": "EROROR",
            "message": "org.apache.catalina.startup.Catalina.start Server startup in 135176 ms\n\tat com.myproject.module.MyProject.badMethod(MyProject.java:22)\n\tat com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)\n\tat com.myproject.module.MyProject.anotherMethod(MyProject.java:14)\n\tat com.myproject.module.MyProject.someMethod(MyProject.java:10)\n\tat com.myproject.module.MyProject.main(MyProject.java:6)",
            "thread": "main",
            "time": "04-Mar-2022 12:42:28.000"
        }
      

まとめ

本記事では、「FluentBitについて理解する」ことを目的として記事を作成しました。 次回の記事では、「FluentBitを実際に動かしたうえで確認した設計・設定ポイント」をお話できればと考えています。

執筆:@kase.teruyoshi、レビュー:@sato.taichiShodoで執筆されました

テックブログの執筆タスクをMicrosoft Power Apps でカレンダー化する

皆さんこんにちは!金融ソリューション事業部 市場系ソリューション1部の寺山です。
かなり温かく、というか暑くなってきましたね。私は、今の時期から冷房に頼ると本場の夏の暑さに耐えられないぞ!という戦略で冷房を使うのを我慢しています。

これまではインフラ/クラウド関連の記事を投稿してきた私ですが、今回は打って変わってローコード関連の内容となります。背景を踏まえてローコードツール:Power Appsを用いて実現した内容をご紹介したいと思います!

背景

私は、昨年度の弊社アドベントカレンダーで記事を投稿した以降に、当テックブログの運営チームに参加しました。
テックブログの運営において、誰がいつ投稿する予定なのか?/投稿の途切れてしまう期間があるか?といった観点で、 執筆状況をカレンダー形式で可視化したいよね という意見が生まれました。 結論として、Microsoft Plannerで執筆タスクを管理することにより上記の目的を達成しようとなりました。理由は以下のとおりです。

  1. Plannerにはカレンダー機能がある。
  2. 機能がシンプルであり、厳密なタスク管理ルールを策定しないことにより執筆者・運営側の双方の負荷にならないよう、執筆管理を運用可能と考えた。

1.でカレンダー形式で可視化するという当初の目的は達成しました。加えて、弊社内でテックブログの活動や執筆ガイドを共有するためのSharePointポータルサイトを構築していたため、そこにカレンダー形式として埋め込むことで、 活動状況を執筆メンバー以外にも共有したいよね! という考えに発展しました。

そこで、プレビュー版ではあるもののWebパーツが提供されているPower Appsを用いてカレンダーを作成し、SharePointに埋め込んでみようと思い立ったのが背景となります。

実際に埋め込んだ PowreAppsは以下のように表示されます!

Power Appsを作成

前述のPower Appsアプリをどのように作成したのかを簡単にご紹介します。
作業はすべてPower AppsのWebコンソールにて行いました。
私はPower Appsについて無知な状態からスタートしたため、Power Appsの各機能やコンポーネント(Power Apps用語ではコントロール)の説明は割愛し、全体的な流れの説明に比重を置かせていただきます。

アプリの作成とコネクタの追加

まずはアプリを作成します。Power Appsのサブメニューより、作成 > 空のキャンバスアプリ > 作成と選択し、アプリ名を設定して空のキャンバスアプリを作成します。
私は利用していませんが、PowreAppsではアプリのテンプレートも提供されています。

形式ですが、SharePointポータルサイトは弊社の社員が業務中にPCから参照するケースの方が多いと考え、タブレットを選択しました。

次はコネクタの追加です。Power Apps で表示/加工するデータは、Office365の他のアプリケーションやサードパーティツール(DBMSやDWHなど)をデータソースとしてコネクタで接続して取得します。
今回利用したコネクタは以下のとおりです。

  • Planner:カレンダーに表示するPlannerタスクやバケットの取得に利用
  • O365Group:タスクの担当者名の取得に利用

コネクタの追加は、サブメニューのデータ > データの追加より行います。データソースに対する認証情報に自身のOffice365アカウントを指定します。

初期化

キャンバスアプリのトップエンティティはスクリーンです。空のキャンバスアプリを作成すると執筆時点のデフォルトではスクリーンのみが作成された状態となります。

私の作成した Power Apps ではスクリーンのOnVisibleプロパティに以下の関数を設定することで、Power Appsの描画時に初期化がされるようにしました。

// 共通して使用する変数の宣言
UpdateContext(
    {
        planId:"<PlannerのPlanID>",
        groupId:"<PlannerのGroupID>", 
        officeGroupId:"<PlannerのGroupID>",
        weekDay:["Sun.","Mon.","Tue.","Wed.","Thu.","Fri.","Sat."]
    }
);
// カレンダーとして表示対象の月の1日と月末の日付を設定
UpdateContext(
    {
        startOfMonth:Date(TextInputYear_1.Text,DropdownMonth_1.Selected.Value,1),
        endOfMonth:Date(TextInputYear_1.Text,DropdownMonth_1.Selected.Value + 1,1)-1
    }
);
// カレンダーに表示する開始日と終了日を計算
UpdateContext(
    {
        fromDate:startOfMonth-(Weekday(startOfMonth,1)-1),
        toDate:endOfMonth+(7-Weekday(endOfMonth,1))
    }
);  
// PlannerとOffice365Groupデータソースから、Plannerタスク一覧、Plannerのバケット一覧、Office365Groupのメンバー一覧を取得してテーブルに格納
UpdateContext(
    {
        tasks:Planner.ListTasksV3(planId,groupId).value,
        buckets:Planner.ListBucketsV3(planId,groupId).value,
        members:O365Group.ListGroupMembers(officeGroupId).value
    }
);
// カレンダーに表示する開始日から終了日までの全日付をテーブルに格納
UpdateContext(
    {
        days:ForAll(Sequence(toDate-fromDate+1),DateAdd(fromDate-1,Value,Days))
    }
)

UpdateContextはスクリーンがスコープとなる変数を宣言/更新する関数です。
以下は各UpdateContextブロックに対する補足です。

  • 1つ目のブロックで変数の値に設定しているPlannerのPlanIDPlannerのGroupIDはPlannerのURLから確認可能です。

  • 2つ目のブロックでは、後述するTextInputYear_1というテキスト入力コントロールの初期値と、DropdownMonth_1というドロップダウンコントロールの初期値から取得しています。

  • 5つ目のブロックのForAllは、第一引数のテーブルに対して第二引数の操作を繰り返す関数です。
    • Sequence関数を使用してカレンダーに表示する開始日から終了日までのインデックスのテーブルを作成して、カレンダーの開始日からインデックスを相対距離として日付を計算することで、カレンダーの開始から終了日までに含まれる全日付のテーブルを作成しています。

Power Apps上での作業は以下のようになります。

カレンダーの描画

カレンダー風に描画するために、2つのギャラリーコントロールを作成しています。
ギャラリーとは、指定したデータに対し、他のコントロールをテンプレートとして展開可能なコンテナ型のコントロールです。

1つ目のギャラリーは、カレンダーの曜日を表示するための水平ギャラリーです。
前のセクションで初期化したweekdayテーブルをギャラリーのItemsプロパティに指定し、ギャラリー内にはテキストラベルコントロールを配置しています。

テキストラベルに表示するTextプロパティで以下の関数を指定することで、曜日の表す列のように描画しています。ThisItemにはギャラリーのItemsプロパティに指定したテーブルの要素が格納されます。

ThisItem.Value

2つ目はカレンダーの各日のボックスを表現する水平ギャラリーです。Itemsプロパティには前のセクションで初期化したdaysテーブルを指定しています。
水平ギャラリーには、まず垂直コンテナを配置し、内部に配置する2つのコントロールの位置関係と描画範囲を調整しています。

コンテナ内に配置する1つ目のコントロールテキストラベルで、以下の関数を設定することでカレンダーの「日付」を表示しています。

Text(ThisItem.Value,"dd")

Plannerのタスクを表示する領域は垂直ギャラリーを使用しました。ギャラリーに表示するデータは、Itemsプロパティに以下の関数を設定し、前のセクションで取得したPlannerタスク一覧から抽出しています。

Sort(Filter(tasks,And(dueDateTime>=ThisItem.Value,dueDateTime<ThisItem.Value+1)),title)

これにより、「各日のボックスを表現する水平ギャラリー」のThisItemに格納されている各日がPlannerの期日に一致するタスクのテーブルが、垂直ギャラリーのデータに設定されます。
垂直ギャラリーのテンプレートに以下のコントロールを配置することでタスクを表示しています。

  • テキストラベル:Plannerタスクのタイトル
ThisItem.title
  • テキストラベルと楕円の図形:Plannerタスクのバケット

    本件では、Planner タスクのステータスではなく、タスクが属するバケットをタスクのステータス情報として採用しました。
    LookUpは、ExcelのVLOOKUPと同じイメージで、第一引数のテーブルから第二引数の条件でデータを抽出する関数です。
    bucketsには前のセクションで初期化したPlannerのバケット一覧が格納されています。

LookUp(buckets,id=ThisItem.bucketId).name
  • テキストラベル:Plannerタスクのオーナー

    membersには前のセクションで初期化したOffice365Groupのグループメンバー一覧が格納されています。

LookUp(members,id=First(ThisItem._assignments).userId).displayName

ポイントとしては、「カレンダーの各日のボックスを表現する水平ギャラリー」のWrapCount(折り返しの数)プロパティに7を指定している点です。

水平方向に7アイテムが表示される毎に折り返されるので、カレンダーのように描画されます。

また、描画範囲を調整するため、上記の2つの水平ギャラリーはコンテナコントロールに配置しています。

表示する年/月のセレクタ

前述までのセクションでカレンダーは表示されます。
初期化の年/月以外のカレンダーを表示可能なよう、セレクタを作成しました。

年はテキスト入力コントロールで利用者に入力してもらう仕様としました。入力された値は、<コントロール名>.Textで参照できます。
初期値として、Defaultプロパティに以下の関数を設定しています。「初期化」セクションでstartOfMonthendOfMonth変数の入力の一方が、この値です。

Text(Today(),"yyyy")

月は、ドロップダウンコントロールから選択する仕様です。プルダウンに表示するリストは、Itemsプロパティに01~12のテーブルとして設定しました。選択された値は、<コントロール名>.Selected.Valueで参照可能です。
初期値として、Defaultプロパティに以下の関数を設定しています。「初期化」セクションでstartOfMonthendOfMonth変数の入力の他方が、この値です。

Text(Today(),"mm")

上記2つのコントロールの入力をカレンダーに反映するボタンコントロールを追加します。
OnSelectプロパティに以下の関数を設定することでカレンダーを更新します。内容は初期化セクションとほぼ同じです。

UpdateContext(
    {
        startOfMonth:Date(TextInputYear_1.Text,DropdownMonth_1.Selected.Value,1),
        endOfMonth:Date(TextInputYear_1.Text,DropdownMonth_1.Selected.Value + 1,1)-1
    }
);
UpdateContext(
    {
        fromDate:startOfMonth-(Weekday(startOfMonth,1)-1),
        toDate:endOfMonth+(7-Weekday(endOfMonth,1))
    }
);
UpdateContext(
    {
        days:ForAll(Sequence(toDate-fromDate+1),DateAdd(fromDate-1,Value,Days))
    }
)

おまけで、利用者の入力をDefaultに戻すリセットボタンも配置しました。

あとはカレンダーらしさが出る修飾

見た目の修飾として以下を行っています。

  • 当日のボックスの日付を強調
  • 土日の背景をグレー表示
  • 年/月セレクタで選択された月以外の日付をグレー表示
    • 例えば、2022年04月が選択された場合、2022/04は4/1~4/30ですが、カレンダー上には3/27~3/31も表示されるため、これらの日付文字の色はグレーになるようにしています。
  • タスクのステータス(Plannerのバケット)毎に表示色を変更
  • 公開済みステータス(Plannerのバケット)の場合はクラッカーの画像を表示

これらは、各コントロールのプロパティにIf関数を使用して実現しています。すべての紹介は割愛しますが、代表として当日日付の強調をご紹介します。
当日日付はテキストラベルコントロールで、Colorプロパティに以下の関数を設定することで、当日日付のテキストラベルの背景色を変更しています。

If(Text(Today(),"yyyy/mm/dd")=Text(ThisItem.Value,"yyyy/mm/dd"),RGBA(20,165,255,1),RGBA(255,255,255,1))

ローコードをはじめてちゃんと触ってみた感想

ご参考までに利用したコントロールの一覧は下図のとおりです。

私はこれまで、Power Appsに限らずローコードツール/プラットフォームというものはほとんど触ったことがありませんでした。
本件で確りと触ってみて、専門的な知識がなくても、それっぽいものを比較的簡単に作れるという便利さが印象的でした。
Microsoft製品だけでも、Power Appsの他にPower Automateなども提供されています。また、弊社のようなベンダーだけでなくエンドユーザー様でも利用しやすい点から、業務効率化やDXの一環として活用できる強力なツールであると実感しました。

最後までご覧いただきありがとうございました。弊社にはローコード開発やお客様の業務効率化/DX推進のご支援も行っている組織もございます。この記事の内容が参考になるか、弊社に興味を持つ機会になれば幸いです。また次の記事でお会いしましょう!

参考文献/記事

執筆:寺山 輝 (@terayama.akira)、レビュー:@sato.taichiShodoで執筆されました

インシデントの振り返り方法としてポストモーテムを導入してみた

こんにちは。電通国際情報サービス(ISID) グループ経営ソリューション事業部の高崎です。

私が担当しているプロダクトの開発・運用中に発生した各種インシデントについて、解決後に再発防止を目的とした振り返りを実施しています。振り返りの方法として、ポストモーテムを導入してみました。 導入して3ヵ月が経過し、導入してよかったと思うことや今後の課題などが見えてきたので、紹介したいと思います。

後述するポストモーテムの内容は下記の書籍を参考にしています。

ポストモーテムとは

インシデントが発生したことによる影響、解決のために行ったアクション、発生した原因、再発防止策などを記録したドキュメントを指します。 ポストモーテムを作成する上で個人を非難してはいけません。ポストモーテムの目的は、誰がインシデントを引き起こしたのかを明らかにすることではなく、なぜインシデントが発生してしまったのかを組織としてはっきりとさせることです。 焦点は人ではなくプロセスと技術にあてられなければなりません。 また、完成したポストモーテムは記載されている教訓が役立つよう、可能な限り広い範囲に共有することが重要です。

ポストモーテムを導入したきっかけ

開発プロジェクトの体制は以下のようになっています。 PM配下に業務領域ごとの開発チーム、その開発チームを横断的に見ているTech Leadがいます。 ※私のポジションはTech Leadです

これまでインシデントが発生した場合、原因となった機能を担当している各開発チームごとに振り返りを実施し、再発防止策を作成していました。そうすると同じチーム内では再発しないのですが、振り返りの内容や再発防止策が他チームに浸透しておらず、同様の原因によるインシデントが他チームで発生するという課題がありました。 上記を解決するための方法を検討していた中で、ポストモーテムを導入してみようと思ったことがきっかけです。

どのようにしてポストモーテムを作成しているか

以下のフォーマットに沿って、関係者が各項目について対面で会話しながら作成しています。ファシリテーターはTech Leadが担当しています。

# インシデント名
## インシデント発生日
## 作成日
## 作者
## ステータス → インシデントの現在のステータス
## サマリ → インシデントの概要
## インパクト → インシデントによってどんな影響があったか
## 検出 → インシデントを検出した方法
## 対応 → インシデントを収束させるために実施したこと
## タイムライン → インシデント発生から復旧までのタイムラインを書く
## 教訓
### うまくいったこと
### うまくいかなかったこと
## 根本原因 → インシデントが発生した根本原因
## 発生要因 → 根本原因が発生した要因
## 再発防止策

特に重要な根本原因・発生要因・再発防止策については、マインドマップを使って深掘りを行っています。 まず根本原因(Where)が何かを突き止めます。次に根本原因となった事象がなぜ発生したのか(Why)を突き止め、最後に再発させないために何をするべきか(How)を決定します。

上記手順を踏まないと、根本原因や発生要因を明らかにしないまま、再発防止策ありきで話が進んでしまい、効果的な対策を打てない可能性があるため、注意が必要です。

問題解決の手段は下記の書籍を参考にしました。

ポストモーテムが作成できたら、可能な限り広い範囲に共有したいため、各開発チームのメンバー全員が集まっているデイリースクラムの中で内容の説明を行います。

導入してよかったこと

ポストモーテムを導入した目的である、同じ原因によるインシデントが他チームで発生するということは今のところ発生していません。また、しっかりと時間をかけて振り返りを行うことで、効果的な再発防止策を作成することができていると感じています。

今後の課題

実際にポストモーテムを作成してみて難しいなと思ったことなのですが、インシデントが発生した原因を突き止めるために、結局は特定のメンバーに当時の様子を細かくヒアリングする必要があります。ヒアリングするメンバーに心理的負担をかけてしまうと、本音を包み隠さず話すことができなくなってしまいます。 いかにメンバーに心理的負担をかけずに振り返りを実施できるかは、ファシリテーターである私にかかっているため、改善していきたいと思っています。

まとめ

まだ導入して3ヵ月しか経っていないため、今後もポストモーテムを実施する上で課題が出てくるかと思いますが、プロダクトを改善するためにどうすればよいかを軸に、改善を進めていきたいと思います。 最後までお読みいただき、ありがとうございました。

執筆:@takasaki.keisuke、レビュー:Ishizawa Kento (@kent)Shodoで執筆されました

組織横断的なSecurity Hub導入に向けて

Xイノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。

以前の記事では、AWS Security Hubを検証した際に気付いたことを山口さんよりご紹介しました。今回はその続編として、Security Hubの実用に向けた取り組みをお話しします。

社内プロジェクトへのSecurity Hub導入

普段ならつい見過ごしてしまいがちな、不十分なセキュリティ設定をきっちり警告してくれるため、ISIDではAWSを使用している全てのプロジェクトにSecurity Hubを導入することを目指しています。

Security Hubは導入するだけなら非常に簡単です。しかし本当に重要なのはその検出結果を確認し、確実に改善に繋げることです。そのためには少なくとも以下のことが必要になります。

  1. 定期的にSecurity Hubの検出結果を確認する運用手順や仕組み
  2. 検出結果の内容の理解
  3. 修正すべきポイントと、修正することによる影響の理解
  4. セキュリティリスクと修正コストを総合的に判断し、対応方針を決定すること

ISIDには数多くのプロジェクトがあり、担当メンバーも異なるため、1~4をプロジェクト内で個別に対応するのは望ましい姿とはいえません。そこで組織横断的な支援を行う体制により、1~4の実施を各プロジェクトに対してサポートしています。

  1. 定期的にSecurity Hubの検出結果を確認する運用手順や仕組み
    → 各プロジェクトの検出結果を集約し、定期的に通知する仕組みの導入(のちに詳述)
  2. 検出結果の内容の理解
    Security Hubのセキュリティ基準をさらに詳細化したノウハウ集の作成と展開
  3. 修正すべきポイントと、修正することによる影響の理解
    → 2.と同じ
  4. セキュリティリスクと修正コストを総合的に判断し、対応方針を決定すること
    → 対応方針の相談窓口の設置

2.と3.については検証用のAWSアカウントを利用して、各検出項目の発火の条件と改善方法を調査してきました。Security Hubのセキュリティ基準の項目は不定期に追加されますので、今後も継続的に調査を行っていくことになります。
4.についてはMicrosoft Teamsに専用のチームを作成し、プロジェクトからの相談を随時受け付ける体制にしています。検出結果に対応するにあたって、各プロジェクトに共通する課題なども見えてくるのではないかと期待しています。

ここからは 1. の「検出結果を集約し、通知する仕組み」について、詳細をお話しします。

検出結果の集約

※注:以下の記述はAWS Organizationsと統合されていない場合です。

Security Hubには複数のメンバーアカウントの検出結果を管理者アカウントに集約する機能がありますので、これを利用します。

アカウント集約

いわゆる管理者アカウントから各プロジェクトのメンバーアカウントに招待を送り、メンバーアカウントから承認することで、Security Hubの検出項目を管理者アカウントからも確認できるようになります。この際、メンバーアカウントにてSecurity HubとConfigの有効化が必要になります。

注意が必要なのは、Security Hubはリージョナルサービスであるため、メンバーアカウントの複数リージョンから結果を集約したい場合はリージョンごとに招待・承認しなければなりません。
複数のメンバーアカウントを取り扱うため、管理者アカウントの方は当然、全リージョンでSecurity Hubを有効にしておきます。

リージョンごとに招待

管理者アカウントに集約された結果を、リージョンを切り替えながら確認するのは手間がかかるので、結果をリージョン間で集約する機能を利用します。
これにより、複数のメンバーアカウントの複数リージョンの検出結果を、1つの管理者アカウントの1リージョンから取得できるようになります。

リージョン間集約

検出結果を通知する仕組みの構築

1箇所に集約された検出結果を、定期的に取得してプロジェクトに通知する仕組みを構築しました。
AWS SDK for JavaScriptを利用し、ECSタスクからSecurity Hub検出結果の取得、レポートの生成と保存、配信までを行います。
通知の仕組み

①ECSタスクを定期実行します
②プロジェクトとAWSアカウントID、および結果配信先を関連付ける情報を取得します。今回はS3バケットCSVファイルとして配置することにしました
③プロジェクトごとに、Security Hubの検出結果を取得します
④⑤プロジェクトごとに検出結果を見やすい形でまとめたExcelレポートを作成し、S3バケットに保存します
⑥レポートにプロジェクトの担当者がアクセスできるように、署名付きURLを生成します
⑦レポートへの署名付きURLをプロジェクト担当者に通知します。今回はメールで配信することにしました
⑧将来的にプロジェクトの対応状況を分析できるように、検出履歴をDynamoDBに保存しておきます
⑨プロジェクト担当者は署名付きURLからレポートをダウンロードします。万が一署名付きURLが漏れても社外の第三者がレポートを取得できないように、バケットポリシーを設定しsource IPをISID社内からのアクセスに限定しておきます

この仕組みにより、以下のようなメリットが発生します

  • プロジェクトが個別にSecurity Hubを確認する負荷を軽減できる
  • 複数アカウントを利用しているプロジェクトの場合、1つのレポートに検出結果がまとまる
  • Excelのレポートを生成するため、検出結果をフィルターしやすい
  • 組織横断的に検出結果への対応状況を集計、分析できる(今後の期待)

構築上のTips

AWS SDK for JavaScript から Security Hub の検出結果を取得する際のTipsを2つ記します。

一度に取得できる検出結果は100件まで

@aws-sdk/client-securityhub モジュールの GetFindingsCommand から受け取る結果の最大数を MaxResults として指定できるのですが、最大100件までという制約があります。

const command = new GetFindingsCommand({
  MaxResults: 100, // 最大100まで
  NextToken: nextToken,
  Filters: filters,
});

100件を超える検出結果を取得したい場合は、結果に NextToken というプロパティに値が入った状態で返ってきます。これを GetFindingsCommand の入力に追加してもう一度送ることで、続きの検出結果を取得できます。

const data = await client.send(command);

if (data.NextToken === undefined) {
  // 結果が100件以内
} else {
  // 結果が100件を超えている
}

すなわち、再帰的に GetFindingsCommand を送信することで、全ての検出結果を取得できます。

import {
  SecurityHubClient,
  GetFindingsCommand,
  AwsSecurityFinding,
  AwsSecurityFindingFilters,
  StringFilter,
} from "@aws-sdk/client-securityhub";

const client = new SecurityHubClient({ region: "ap-northeast-1" });

export async function getNextFindings(awsAccountIds: string[], nextToken?: string): Promise<AwsSecurityFinding[]> {
  const accountsFilter: StringFilter[] = awsAccountIds.map((id) => {
    return { Value: id, Comparison: "EQUALS" };
  });

  // AWSアカウントID、レコードの状態、ワークフローステータスでフィルタリング
  const filters: AwsSecurityFindingFilters = {
    AwsAccountId: accountsFilter,
    RecordState: [{ Value: "ACTIVE", Comparison: "EQUALS" }],
    WorkflowStatus: [
      { Value: "NEW", Comparison: "EQUALS" },
      { Value: "NOTIFIED", Comparison: "EQUALS" },
    ],
  };

  const command = new GetFindingsCommand({
    MaxResults: 100,
    NextToken: nextToken,
    Filters: filters,
  });
  // コマンド送信
  const data = await client.send(command);
  const findings: AwsSecurityFinding[] = data.Findings ?? [];

  if (data.NextToken === undefined) {
    return findings;
  } else {
    // 100件を超える場合は再帰的に呼び出す
    return findings.concat(await getNextFindings(awsAccountIds, data.NextToken));
  }  
}

一度にフィルタリングできるアカウントIDは20件まで

プロジェクトごとに検出結果をSecurity Hubから取得したいので、AWSアカウントIDで検出結果をフィルタリングしてクエリしていますが、アカウントIDの数が多いと以下のようなエラーになりました。

UnhandledPromiseRejectionWarning: InvalidInputException: Invalid parameter 'Filters'. data.AwsAccountId should NOT have more than 20 items

アカウントIDは一度に20件までしかフィルタリングに利用できないとのことなので、20件を超えるアカウントIDの検出結果を取得したい場合は、20件以下に分割してから複数回クエリする必要があります。

今後に向けて

今回お話しした仕組みの運用はまだ始まったばかりで、今後新たな課題が発生するかもしれません。ISIDでは引き続き、組織横断的にAWS環境をセキュアにするための取り組みを実施していきます。

執筆:@kou.kinyo2、レビュー:@higaShodoで執筆されました

ブロックチェーン週報4/22

電通国際情報サービス、クロスイノベーション本部、オープンイノベーションラボの比嘉康雄です。 それでは、ブロックチェーン週報4/22いってみようか。

NFTラッシュ

NFTプロジェクトのニュースがラッシュですね。BAYC(Bored Ape Yacht Club)ベースのアニメが映画で全3部作とか、嫌な予感しかしませんね。失敗してもNFTのせいにされませんように。

米コインベース、人気NFT「BAYC」のアニメ映画製作へ

メジャーリーグ(MLB)のNFTが販売されます。 これは、NBA Top Shotの柳の下のどじょうを狙ったものですが、どうなるか要注目。NBA Top Shot自体、2021年の8月以降、下降線です。

米メジャーリーグ今季NFT、キャンディデジタルで発売

WWE(World Wrestling Entertainment, Inc.)も同じスキーム。

WWEがNFT事業展開へ

日本でもIPのNFT化が進んでますね。ただ、IPがNFTになって本当に意味があるのかはみんな考える必要があると思います。単に売れればOKは良くない。

「LINE NFT」 吉本、パトレイバーなど限定NFT動画、約4万点を順次販売

パティ・ボイドの写真のNFTは、買う人いそうな気はする。雑談ですが、テイラー・スウィフトはパティ・ボイドのファンらしいです。顔がちょっと似てるよね。

ビートルズの「女神」パティ・ボイド、NFTで革命を再び

時代のミューズとともに:テイラー・スウィフトが憧れのパティ・ボイドと対談

きゃりー君もか。

きゃりーぱみゅぱみゅ、米国の音楽祭でメタバースNFTとコラボ

浜崎あゆみとピコ太郎もですか。だんだん疲れてきた。

浜崎あゆみ、ピコ太郎がNFT・メタバースに──エイベックスがThe Sandboxに「エイベックスランド」構想

その他のニュース

観光特化デジタル通貨「ルーラコイン」、3エリアで実証成功--平均消費単価1万4900円

「アバランチ」開発のアバラボ、3.5億ドルの調達に動く-関係者

歩いてトークン稼ぐSTEPN、プレーに必要なNFTスニーカーが高騰──1足最低14万円

高すぎNFTは、NFTに価値を認めているんじゃなくて、転売目的な気がして好きになれない。本当に価値を認めて買っている人もいると思いますが。

執筆:@higaShodoで執筆されました

複雑な承認ワークフローシステムをSalesforceで構築できるようになりました!

Salesforceで承認ワークフローシステムを構築しているまたはしようと思っている皆様、ワークフロー要件が複雑なためにSalesforceの承認プロセス等の標準機能では構築できず、専用のワークフローツールを使用していませんか?

Spring22のアップデートで、Salesforceの機能を利用して高度な承認ワークフローを構築できるようになりました。

今回は、その新機能である『Flow Orchestrator』と既存機能を組み合わせることでどんなことができるのかを紹介します。

1. なぜ、今までSalesforceで複雑な承認ワークフローを作成できなかったのか?

1-1. ワークフローシステムツールでよくある機能

承認ワークフローを構築する際、以下のような要件を思い浮かべる方が多いと思います。

・申請内容によって承認の流れを分岐する機能

・承認者の複数設定や代理承認者を設定する機能

・承認状況をリアルタイムに確認でき、承認時のコメントを受信する機能

しかし、これらの機能を組み合わせ、複雑な業務要件を満たす承認ワークフローをSalesforceで実現することは困難でした。

1-2. なぜ、今まではSalesforceで複雑な承認ワークフローを実現できなかったのか? 

日本は特に、承認ワークフローが複雑だと言われています。例えば、最終承認者に到達するまでの段階の多さが特徴としてあげられます。また、申請金額の大小により、承認の段階の数が変わることもあるでしょう。

もともと、Salesforceには承認プロセスという簡単なワークフローを作成できる機能が用意されています。しかし、承認プロセスでは、承認時はコメント以外入力できなかったり、複雑な承認者の設定ができなかったりと制限がありました。そのため、複雑なワークフローの構築は専用のワークフローツールを使用するしかなかったのです。

2. 『Flow Orchestrator』の概要

『Flow Orchestrator』とは、Spring22で正式リリースされた複雑なワークフローを作成できるSalesforceのローコード開発ツールです。

この新機能とSalesforceの既存機能を組み合わせることで、例えば、以下を実現できます。

・金額等の条件によりフローを分岐できる機能

Salesforce上からリアルタイムで承認状況を確認できる機能

・承認時の入力項目を自由にカスタマイズできる機能

・ユーザ/グループ単位の割り当て・手動割り当て・代理承認者割り当てができる機能

・申請者/承認者に通知する機能

・複数のユーザが並行して承認する機能

今回、これらの機能を取り込んだ承認ワークフローシステムを実装してみました。

3. 簡単な承認ワークフローシステムを実装してみた

3-1. 承認フロー図

今回は下図のようなフローを想定し作成してみました。

この後のGIFでは、フロー図の赤線の流れをお見せします。

3-2. 実際の承認の流れ

※今回はDeveloper環境のため、ユーザを使い回しています。ユーザの氏名に役職を記載しています。

①申請者は交通費(250,000円)を申請する

②グループ長は長期休暇中のため、グループ長代理が承認する

次承認者(部長)を手動で選択する

③部長が承認する

次承認者(事業部長)は手動で選択する

※ 事業部長・社長に並行して承認依頼されていることが確認できる

④事業部長が承認する

⑤社長が承認する

⑥事業部長・社長が承認を終えたので、経理担当者が決済をする

以上の通り、実装しました。

4. まとめ

Salesforceの機能でも日本の複雑な承認ワークフローシステムを実現できることを理解していただけたでしょうか?

承認ワークフローシステムをSalesforce上で構築するメリットとしては、Salesforce上で全ての情報を参照できることだと考えております。メール通知はもちろん、Chatter通知も可能です。また、レコードの詳細画面にパス(ステータス)を設定することで、承認状況をリアルタイムに表示できます。

今回の記事には書かれていない皆様の会社特有のワークフロー機能も実現できるかもしれません。

ぜひ、Salesforceでワークフローシステムを構築する際は、『Flow Orchestrator』の利用を検討してみてください!

5. 構築の際に参考になるSalesforce公式サイト

Salesforce Spring'22 リリースノート | オーケストレータ (正式リリース)

Trailblazer Community のグループ | Orchestrator

TrailheaDX | Flow and Orchestrator (1)

TrailheaDX | Flow and Orchestrator (2)

執筆:@kitakado.maho、レビュー:@kumakura.koki.isidShodoで執筆されました