コンテンツにスキップ

ファンクションの書き方

このページでは Craft Functions のコードの書き方を説明します。

Craft Functions の記法

Craft Functions のコードの記法を説明します。

Craft Functions の実行ランタイム

Craft Functions は Node.js の実行環境上で動作します。現在サポートしているランタイムは以下の通りです。

  • Node.js v18
  • Node.js v20

ランタイムバージョンの詳細については、Node.jsのドキュメント、ブログ等を参照してください。

Node.js — Run JavaScript Everywhere

Craft Functions のエントリポイント

Craft Functionsでは、次のように定義した関数がエントリポイントとなります。

export default async function (data, { MODULES }) {
// この関数内に処理を記述する
// Async Functionとして定義しているので、awaitで呼び出す処理が必要
}

この関数は次の要件を満たします。

  • 関数名は指定しません。 export default として定義します。
  • 引数は dataMODULES 要素を持つオブジェクト ( { MODULES } ) の2つです。
  • 基本的には async function として定義します。

入力 (data)

Craft Functions は外部からの入力として、引数 data の値を受け取ります。data の型は object 型もしくは string 型です。多くの場合は object 型で値を受け取りますが、以下の場合は string 型の値となります。

  • サーバーサイドアクションで JSON 形式以外の文字列を渡した場合
  • Craft Scheduler で Function を定期起動する場合

モジュールの利用

Craft Functions では以下の方法でモジュールが利用できます。

  • MODULES 引数の要素として取り出して利用する
  • npm modules をコード内で import する
  • Node.js の標準モジュールを利用する

これらのモジュールの使用方法を説明します。

MODULES の利用

Craft Functions の独自モジュールは MODULES 引数の要素として取り出せます。

const { initLogger, secret } = MODULES
const logger = initLogger({ logLevel: 'DEBUG' });
logger.log('infomational log');
const { KARTE_SECRET_KEY: secretValue } = await secret.get({ keys: [ 'KARTE_SECRET_KEY' ] });#

MODULES で利用できるモジュールは Craft Functions で利用できるモジュール で説明します。

npm modules を import する

npm modules を利用する際にはファンクション編集画面の modules で利用したいモジュールとバージョンを JSON 形式で指定します。

例えば、Google Cloud Pub/Sub の Node.js ライブラリを利用する場合は modules で以下のように指定します。

{
"@google-cloud/pubsub": "3.4.1"
}

複数の npm modules を利用する場合は、 modules で利用する module を JSON 形式で列挙します。

{
"@google-cloud/pubsub": "3.4.1",
"@google-cloud/bigquery": "6.1.0"
}

npm modules を Craft Functions 内で利用する際は、Craft Functions のコード内で import 文を使い呼び出します。

import { PubSub } from "@google-cloud/pubsub";
export default async function (data, { MODULES }) {
// clientConfigの詳細な設定方法は省略
const clientConfig = {
projectId: "hogehoge",
credentials: {
client_email: "fugafuga",
private_key: "foobar",
},
};
const topic = new PubSub(clientConfig).topic("topic-name");
// 略
}

npm modules の制限

  • 現在 Craft で利用できる npm modules については、 利用できる npm modules をご参照ください。
  • 各 module の任意のバージョンが指定できます。ただし、チルダ ~ キャレット ^ によるバージョン指定はできないのでご注意ください。
    • 存在しないバージョンを指定するとファンクションの作成に失敗します。

Node.js の標準モジュールを利用する

Node.js の標準モジュールとして crypto が利用できます。利用する場合は、Craft Functions のコードで import します。

import { randomUUID } from "crypto";
export default async function (data, { MODULES }) {
const { initLogger } = MODULES;
const logger = initLogger({ logLevel: "DEBUG" });
logger.log(randomUUID());
}

関数の同期/非同期

Craft の大半のユースケースでは、外部システムへのアクセスが発生します。JavaScript では特に理由がない限り外部へのリクエストは非同期処理で実現するため、多くの場合、Craft Functions は Async Function として定義します。

入力を単純にログとして出力するだけの場合、非同期処理は必要ありません。このような場合、Craft Functions は同期関数として定義できます。

export default function (data, { MODULES }) {
const { initLogger } = MODULES;
const logger = initLogger({ logLevel: "DEBUG" });
// for debbuging.
logger.log(data);
}

Craft Functions の性質

Craft Functions でプログラムを作成する際に考慮すべき性質について説明します。

Craft Functions は at least once で実行される

Craft は at least once で Craft Functions を実行します。これはトリガーが発したイベントあたり「少なくとも 1 回」は Function を実行することを意味します。したがって、トリガーあたり 2 回以上 Function が実行されることもあり得ます。

exactly once (トリガーのイベントあたり 1 回だけ Function を実行する)を実現する場合は、トリガーから受け取るリクエスト ID を Craft KVS や外部のデータベースサービスに書き込み、コード内で書き込んだレコードの有無を判定します。

Craft Functions は実行環境を使い回すことがある

Craft Functions は実行環境を使い回すことがあります。同一環境上で実行する場合、Craft Functionsは関数外で定義した値を再計算せず、前回実行時と同じ値を使います。したがって、関数定義の外側で処理時間のかかる処理を行うことで、ファンクションを複数実行した際の平均処理時間を短縮できます。

一方で、実行ごとに異なる値を使う必要がある場合はキャッシュによって意図しない動作を引き起こすので注意してください。次のコードは uuid にランダムな UUID を設定していますが、同一実行環境では上記の挙動により同じ値を返却してしまいます。

import crypto from "crypto";
// 関数の外で uuid を定義
const uuid = crypto.randomUUID();
export default function (data, { MODULES }) {
// 同一実行環境でuuidはキャッシュされるため、短時間に複数回実行すると同じ値が返ることがある
return uuid;
}

Craft Functions は非同期処理を自動的に完了しない

非同期な処理を行う際は、 必ず関数内で非同期処理の完了を待ってください。 非同期な処理を待機しないと、Craft Functions 実行時に次のような不具合が発生することがあります。

  • ファンクションの実行が完了しない、もしくは完了するまでに著しく時間がかかる
  • 次回の実行時に未完了の処理を再開するなど、意図しない挙動となる
  • エラー発生時にログが記録されない

例えば次のように Promise チェーンで非同期処理を実装した場合、ファンクションは Promise チェーン内の非同期処理の完了を待たずに終了することがあります。加えて、次回の実行時に未完了の処理を再開して意図しない挙動をすることがあります。

export default function (data, { MODULES }) {
const { initLogger } = MODULES;
const logger = initLogger({ logLevel: "DEBUG" });
// Craft Functionsでは次のfetch APIの呼び出し方は非推奨
// (data) => logger.log(data) が完了するまで待機しなければならない
fetch("http://example.com/movies.json")
.then((response) => response.json())
.then((data) => logger.log(data));
}

この場合、Promise チェーンで書かれた処理を await で待機することで同期的な処理にできます。

export default function (data, { MODULES }) {
const { initLogger } = MODULES;
const logger = initLogger({ logLevel: 'DEBUG' });
// await で処理の完了を待つ
await fetch("http://example.com/movies.json")
.then((response) => response.json())
.then((data) => logger.log(data));
}

また、一般的に Promise チェーンは async/await を使った処理に書き換えられます。非同期処理を実装する際は、await 式を適切に利用してください。

export default function (data, { MODULES }) {
const { initLogger } = MODULES;
const logger = initLogger({ logLevel: 'DEBUG' });
// async/await を使って書き換える
const response = await fetch("http://example.com/movies.json");
const resData = await response.json();
logger.log(resData);
}

Craft Functions の制約

Craft Functions には以下の制約があります。

  • Craft Functions 実行時に渡せる入力データの最大サイズは、10KB です。
    • 入力データのサイズは、UTF-8 エンコードの JSON 文字列として計算します。
    • サイズ上限を緩和するプランも用意しておりますので、お問い合わせください。
  • Craft Functions の 1 実行あたりの起動時間の上限は 10 秒です。
  • Craft Functions では Node.js でサポートされている global object の利用を一部制限しています( requireprocess など)。
  • デフォルトで Craft Functions の実行環境の Public IP アドレスは不定です。Public IP アドレスを固定する場合は固定IPアドレスオプションをご利用ください。

Fetch API の利用

Craft Functions 上で外部のリソースにアクセスする場合は、Node.js の fetch() を利用します。

Global objects | Node.js v20.12.2 Documentation

例: JSON データを取得する

OpenWeatherMap API を呼び出して緯度経度から天気情報データをjson形式で取り出します。

// 緯度(lat) 経度(lon) をもとに OpenWeatherMap から天気情報を取得する
const api_key = "API_KEY"; // OpenWeatherMapのAPIキーを指定する。
const lat = 35.6696559; // 北緯 35.6696559 度
const lon = 139.7639876; // 統計 139.7639876 度
const ret = await fetch(
`https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${api_key}`
);
const weather_forecast = await ret.json();

例: CSV ファイルを取得する

内閣府が公開している祝日一覧を取得する例です。

// 内閣府のWebサイトにある国民の祝日一覧CSVを取得する
const HOLIDAYS_LIST_URL =
"https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
const res = await fetch(HOLIDAYS_LIST_URL, {
method: "GET",
headers: {
Accept: "text/csv",
},
});
// 必要に応じてエラー処理を行う
if (res.status !== 200) {
logger.error(res);
throw new Error("cannot get holidays data.");
}
// 祝日一覧のCSVから日時情報のみ取得する
const ret = (await res.text())
.split("\\n")
.slice(1)
.map((line) => line.split(",")[0]);

KARTE のサーバーサイド API (API v2) の利用

Craft Functions では KARTE のサーバーサイド API (API v2) が利用できます。利用の際には modules で api を指定し、API リファレンスの Node の実行例に従ってコードを記述します。

例: Send event to KARTE API (/v2/track/event/write) を実行する

modules で api を指定します。

{
"api": "5.0.8"
}

コードで API を呼び出します。

  • api() の引数には API リファレンスの Node のリクエスト例(下図)に基づいて設定します。
  • 参考:Send event to KARTE
import api from "api";
export default async function (data, { MODULES }) {
const { initLogger } = MODULES;
const logger = initLogger({ logLevel: 'DEBUG' });
// 実際の引数は APIリファレンスNode Example を参照
const insight = api("@dev-karte/vx.y#zzzzzzzzzzzzz");
insight.auth("token_value"); // 実運用の際は、Secret Managerに設定したアクセストークンを利用します。
const res_data = await insight.postV2TrackEventWrite({
keys: { user_id: "test01" },
event: {
values: { key1: "value1", key2: "value2" },
event_name: "test_event",
},
});
logger.log(res_data);
}