EdgeWorkers の API オーケストレーションのサンプルコードを動かす

GitHub に公開されている Akamai EdgeWorkers のサンプルの中で、人気の高いコードの一つが API Orchestration です。API オーケストレーションを使うと、たとえば、ユーザーのリクエストをトリガーに、外部に提供している複数のAPIを呼び出し、マッシュアップした情報を返す、といったことができます。オリジンサーバーに負荷が集中する処理を中間層にオフロードしたり、オリジンサーバーの変更をしないで柔軟性を高めたりすることができます。本記事は、API オーケストレーションのサンプルコードを動かすための補足説明です。
![EdgeComputing_patterns3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/d01ebe35-c0d0-8419-f05a-6cf8686fc657.jpeg)
EdgeWorkers のユースケースや事例は Qiita にて実施したイベントの記事を参照ください。

https://zine.qiita.com/event/202111-akamai/

# API オーケストレーションとは
API オーケストレーションとは、アプリケーションインテグレーションの一つのパターンです。クライアントに対して、バックエンドの API エンドポイントを意識せずに応答します。クライアントとバックエンドの API が密結合していないので、中間層で柔軟に仕様変更できます。API オーケストレーションは一つ、もしくは複数の API を呼びます。EdgeWorkers は API オーケストレーションを実現するためのイベントハンドラーを提供します。EdgeWorkers から呼ばれる API リクエストは``サブリクエスト``とも呼ばれます。
![responseprovider-event-handler-v1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/a166b816-540f-1337-283a-7b8105bc3de2.png)
API オーケストレーションのパターンで利用されるのが EdgeWorkers が提供する ``ResponseProvider`` イベントハンドラーです。上図のように、``ResponseProvider`` の手前にはキャッシュ層があるので、一度応答したメッセージはキャッシュとして返すことも可能となります。

# サンプルコードの場所
EdgeWorkers のサンプルコードは [https://github.com/akamai/edgeworkers-examples](https://github.com/akamai/edgeworkers-examples) にあります。その中で、本記事で参照するサンプルコードは下記にあります。

https://github.com/akamai/edgeworkers-examples/blob/master/edgecompute/examples/orchestration/api-orchestration/main.js

# 知っておくべきこと
EdgeWorkers は Akamai CDN (Contents Devliery Network) 上で動くため、CDN の知識を必要とします。CDN 担当者と EdgeWorkers 開発者の作業と権限は分離できるように設計されていますが、コードを開発する上で理解しておくことがありますので、以下に簡単に整理します。

- Akamai CDN の``プロパティ``の概念を理解する
- EdgeWorkers からリクエストするサブリクエストのエンドポイントは Akamai から配信されていること
- サブリクエストは HTTPS が使われる
- サブリクエストは JSON を返すことが期待されている

## プロパティの概念を理解する

- 特定のホストに対して Akamai CDN を使うための設定はプロパティという単位で管理されます。
- プロパティには、複数のルール (rule) が含まれます。
- ルールは、クライテリア (Criteria) とビヘイビア (Behavior) で表現されます。

EdgeWorkers のコードを起動するには、 特定の Criteria 条件に合致した場合に Behavior で指定された EdgeWorker ID が起動されるように、事前に rule を定義する必要があります。管理者ポータルには Property Manager が提供されており、プロパティに 複数の rule を簡単に定義できます。Akamai の特徴の一つは、Rule で表現できる Criteria と Behavior が多岐に渡っていることでもあります。

:::note warn
Property Manager で EdgeWorkers を起動するように設定するには、CDN 担当者にご相談ください。
:::
参考までに Criteria と Behavior の例を簡単に紹介します。実際にはこのように日本語で記述できるわけではありませんが、概念として理解してください。

### Criteria の例

```
パス が /api/* である
かつ、
リクエスト Cookies に token が存在する
```

```
パス が /api/v1 である
もしくは
パス が /api/v2 である
```

### Behavior の例

```
キャッシュ設定をバイパスする
```

```
EdgeWorker ID : XXX を起動する
```



# サンプルコード
GitHub に公開されているサンプルコードです。
```javascript
/*
(c) Copyright 2020 Akamai Technologies, Inc. Licensed under Apache 2 license.
Version: 0.1
Purpose:  Combine 3 api endpoints returning JSON into a single JSON response.
*/

import { httpRequest } from 'http-request';
import { createResponse } from 'create-response';

const endPoint1 = '/api/example/endpoint1';
const endPoint2 = '/api/example/endpoint2';
const endPoint3 = '/api/example/endpoint3';

async function getJSON (url) {
  const response = await httpRequest(`${url}`);
  if (response.ok) {
    return await response.json();
  } else {
    return { error: `Failed to return ${url}` };
  }
}

// The responseProvide function generates a response, acting as a "surrogate origin".
// The response may be cached according to the caching rules configured in the property.
export async function responseProvider (request) {
  const result = {};

  // Make all requests in parallel to retrieve content.
  const endPointResult1 = getJSON(endPoint1).then(json => { result.endPoint1 = json; });
  const endPointResult2 = getJSON(endPoint2).then(json => { result.endPoint2 = json; });
  const endPointResult3 = getJSON(endPoint3).then(json => { result.endPoint3 = json; });

  // Wait for all requests to complete.
  await Promise.all([endPointResult1, endPointResult2, endPointResult3]);

  // Return merged JSON as the response.
  return Promise.resolve(createResponse(
    200,
    { 'Content-Type': ['application/json'] },
    JSON.stringify(result)
  ));
}
```

このコードで注意するのは、次の三行です。
```javascript
const endPoint1 = '/api/example/endpoint1';
const endPoint2 = '/api/example/endpoint2';
const endPoint3 = '/api/example/endpoint3';
```
こちらはホスト名が書かれていない形式ですので、EdgeWorkers が起動されるプロパティ上のパスとなります。つまり、ホスト名が ``www.foo.com`` であれば、``https://www.foo.com/api/example/endpoint1`` の API エンドポイントが存在する必要があります。そして、HTTPS が必ず利用されることに注意してください。

すでに Akamai を利用しているホスト名の ``api.foo.com`` があれば、フルパスの ``https://api.foo.com/api/example/endpoint1`` と記述します。

このエンドポイントをサードパーティのドメインで利用する場合は、そのドメインが Akamai から配信されている必要があります。これは、セキュリティに配慮しているからです。

例えば、次のように Akamai から配信されていない URL は EdgeWorkers からは呼び出せません。
``https://my-json-server.typicode.com/110hideki/mockapi/db``

この API を EdgeWorkers から呼び出したい場合は、このホストをアカマイから配信できるように設定する必要があります。例えば、次のような URL を生成します。
``https://api.foo.com/myapi/db.json``

このような URL を作成するには、新たなプロパティを作成し、証明書も作成し、``my-json-server.typicode.com`` をオリジンとする ``api.foo.com`` のようなホストを有効にする必要があります。

新たなプロパティを設定しなくても、EdgeWorkers を起動したいホストのプロパティにて、マルチオリジンとして設定することも可能です。つまり、特定の条件ではオリジンサーバーを ``my-json-server.typicode.com`` にすることで、結果として Akamai 経由で API を呼び出すことができます。下図のような構成を取ることでサードパーティのドメインでもアカマイ経由で配信できます。
![multi-origin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/bc15fdf2-3549-5f14-c436-4f999a7e476a.png)
この例の Criteria と Behavior を後ほど紹介します。

## マルチオリジンの Criteria と Behavior の例
Akamai 化されているホスト名を ``www.foo.com`` とする。
### やりたいこと
``https://www.foo.com/110hideki/mockapi/db`` の API エンドポイントを作成し、このエンドポイントで EdgeWorkers を起動する。この時に EdgeWorkers からサブリクエストとして呼ばれるバックエンドの API を次のURLとする。
 ``https://my-json-server.typicode.com/110hideki/mockapi/db`` 
### Criteria
```
パス が /110hideki/*
```
### Behavior
```
Origin Server は my-json-server.typicode.com とする
```

### EdgeWorkers のコード例

```javascript
const endPoint1 = '/110hideki/mockapi/db';
// const endPoint2 = '/api/example/endpoint2';
// const endPoint3 = '/api/example/endpoint3';
```

```javascript
  // Make all requests in parallel to retrieve content.
  const endPointResult1 = getJSON(endPoint1).then(json => { result.endPoint1 = json; });
//  const endPointResult2 = getJSON(endPoint2).then(json => { result.endPoint2 = json; });
//  const endPointResult3 = getJSON(endPoint3).then(json => { result.endPoint3 = json; });
```

```javascript
// await Promise.all([endPointResult1, endPointResult2, endPointResult3]);
await Promise.all([endPointResult1);
```

# サンプルコードの解説

API オーケストレーションのサンプルコードは ``ResponseProvider`` イベントハンドラーが使われます。 そして、そのコードの中では ``httpRequest`` の組み込み関数が呼ばれています。実際に呼ばれているコードです。

```javascript
const response = await httpRequest(`${url}`);
```

httpRequest については次のページを参照ください。

https://techdocs.akamai.com/edgeworkers/docs/http-request#http-request

EdgeWorkers から呼び出された API エンドポイントからは次のように JSON で返されることを期待されています。
```javascript
    return await response.json();
```

EdgeWorkers から複数の API エンドポイントを非同期処理で呼び出し、全ての結果をクライアントに返すように次のコードが呼び出されています。

```javascript
// await Promise.all([endPointResult1, endPointResult2, endPointResult3]);
await Promise.all([endPointResult1]);
```

最終的に ``responseProvider`` イベントハンドラーは次のコードを呼び出し、APIの結果を返します。

```javascript
 return Promise.resolve(createResponse(
    200,
    { 'Content-Type': ['application/json'] },
    JSON.stringify(result)
  ));
```

サンプルコードの中には EdgeWorkers が用意している組み込み関数が使われています。これらを使うために、コードの最初に次のようにインポートされています。
```javascript
import { httpRequest } from 'http-request';
import { createResponse } from 'create-response';
```
最後に ``createResponse`` が呼ばれていますが、この関数を利用するには 次の行が必須です。
```javascript
import { createResponse } from 'create-response';`
```
create-response のビルトイン・モジュールに関しては次のページを参照ください。

https://techdocs.akamai.com/edgeworkers/docs/create-response

``httpRequest`` 関数についても前述しましたが、同様にインポートが必須となります。

# まとめ
EdgeWorkers から外部の API エンドポイントを呼び出すためのヒントを紹介しました。API オーケストレーションのサンプルコードは３つの API エンドポイントの JSON データを繋げて返す単純なケースとなっています。実際のケースでは 条件によって API エンドポイントを動的に変更したり、API エンドポイントからの応答によって、クライアントに返すレスポンスボディを返すなど、ビジネス要件によってロジックが実装されると思われます。EdgeWorkers はイベント数課金であり、動作確認程度の検証ではほぼ無償で使えますので是非お試しください。