Craft Functionsの書き方

Craft Functionsのプログラムを作成するために必要な情報(記法、制約、性質)を説明します。

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

Craft Functionsの記法

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

Craft Functionsの関数定義

Craft FunctionsはNode.js 18の実行環境上で動作する関数として定義します。以下に基本的な関数の記述例を示します。

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

入力 (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形式で指定します。

ファンクション編集画面。modulesに利用するnpm moduleを指定する。

ファンクション編集画面。modulesに利用するnpm moduleを指定する。

例えば、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は実行環境を使い回すことがあります。同一環境上で実行する場合、関数外で定義した値は再計算することなく使いまわされます。したがって、関数定義の外側で処理時間のかかる重い処理を行うことで、コールドスタート時以外の処理時間を短縮できます。

一方で、実行ごとに異なる値を使う必要がある場合はキャッシュによって意図しない動作を引き起こすので注意してください。次のコードは 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' });

  // 次の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 = response.json();
  logger.log(resData);
}

Craft Functionsの制約

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

  • Craft Functions実行時に渡せる入力データの最大サイズは、10KBです。
    • 入力データのサイズは、UTF-8エンコードのJSON文字列として計算します。
  • Craft Functionsの1実行あたりの起動時間の上限は10秒です。
    • 上記時間を超えても関数実行が完了しなかった場合、同じデータで関数が再実行されることがあります
    • 起動条件を緩和するプランも用意しておりますので、お問い合わせください
  • 現状、Craft FunctionsではNode.js 18でサポートされているすべてのglobal objectを使えるわけではありません。 requireprocess などの利用を制限しています。
  • Craft Functionsの実行環境のPublic IPアドレスは固定できません。リクエスト元IPを制限しているエンドポイントにアクセスする必要がある場合は、独自にプロキシを構成するといった対応をする必要があります。

エラーハンドリング

関数内で発生したエラーはロギングされます。
内部的にもFunctionの失敗として記録されるため、エラーハンドリング後は再度エラーをthrowすることを推奨します。

export default function (data, { MODULES }) {
  try{
    const ret = await someFunc();
  }catch(error){
    someRecoveryFunc();
    throw error;
  }
}

Fetch APIの利用

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

Global objects | Node.js v18.14.1 Documentation

例: JSONデータを取得する

OpenWeatherMap APIを呼び出してJSONデータを取り出す例です。

📘

OpenWeatherMap APIの利用方法については、公式ページをご確認ください。
Сurrent weather and forecast - OpenWeatherMap
コード内で利用しているAPI仕様の最新情報は上記ページからご確認ください。

// 緯度(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 { logger } = MODULES;
  // 実際の引数は 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);
}
/v2/track/event/write APIのリファレンス

/v2/track/event/write APIのリファレンス