Salesforce B2C Commerce で Hooks を使ってみる


※ これから記載する事項は、私が所属する会社とは一切関係のない事柄です。


本記事では、B2C CommerceのHookとは何か。また利用方法について紹介したいと思います。
Info CenterのHooksに関するヘルプは[こちら](https://documentation.b2c.commercecloud.salesforce.com/DOC1/index.jsp?topic=%2Fcom.demandware.dochelp%2Fcontent%2Fb2c_commerce%2Ftopics%2Fsfra%2Fb2c_sfra_hooks.html&resultof=%22%68%6f%6f%6b%73%22%20)。


## Hooks とは？

アプリケーションのロジックの中に機能を組み込みたいときや、特定のイベントが発生した時に機能を組み込みたいときに利用します。extension point(以下、Hook名)というのを指定して機能の追加や実行を行います。
Hooksのタイプには下記の3種類あります。

* SCAPI / OCAPI Hooks
* B2C Commerce Hooks
* Custom Hooks

### SCAPI/OCAPI Hooks

SCAPI/OCAPI HooksはSCAPIやOCAPIの特定のAPIが呼ばれた際に発生する事前に定義されたHook名に対して機能を追加できます。（SCAPIとOCAPIについては[以前紹介したQiita](https://qiita.com/syunyama/items/060f5777b0fc04e4cea1#scapi%E3%81%A8ocapi%E3%81%AE%E9%81%95%E3%81%84)をご覧ください）
例えば、注文を作る(`/orders` にPOSTする)ときに `dw.ocapi.shop.order.afterPOST` というHook名のHookが実行されるので、そのHook内に外部の注文管理システムへ注文情報を連携する機能を追加することができます。
現状、`dw.ocapi~` から始まっていますが、OCAPIだけでもなく、SCAPIでも使えますのでご留意ください。
どのようなHookがあるかはHooks名の一覧([OCAPI](https://documentation.b2c.commercecloud.salesforce.com/DOC2/topic/com.demandware.dochelp/OCAPI/current/usage/Hooks.html?resultof=%22%48%6f%6f%6b%73%22%20%22%68%6f%6f%6b%73%22%20), [SCAPI](https://developer.salesforce.com/docs/commerce/commerce-api/guide/hook_list.html))をご覧ください。また、SCAPIで利用する際は、[こちらのヘルプ](https://developer.salesforce.com/docs/commerce/commerce-api/guide/extensibility_via_hooks.html)をご覧ください。（改めてSCAPIの利用時の注意点は後述します。）


### B2C Commerce Hooks

B2C Commerce Hooksはシステム内での特定の機能にロジックを追加したいときに使用します。
例えば、注文番号が作成される際に`dw.order.createOrderNo`というHookが呼ばれるので、注文番号にプリフィックスを付け足すことができます。
他にも多くのB2C Commerce Hooksありますが、Info Centerで「Hooks」で検索して、「Class ~Hooks」と書かれているページを参照してみてください。


### Custom Hooks

一方Custom Hooksは、開発者自身でHook名を定義し、機能を追加することができます。
例えば、`app.customer.registered` というHookを定義して、SFRAで顧客登録した際に、登録用のコントローラからそのHookを実行し、外部システムに顧客情報を連携することができます。



## Hooksの使い方

Hooksはカートリッジ内に実装していきます。Hookの使い方のサンプルは[Github](https://github.com/SalesforceCommerceCloud/sfcc-hooks-collection/tree/main/commerce-cloud-code/plugin_hooktacular)をご覧ください。（要Account Manager アカウント）

:::note warn
※ カートリッジの作成方法やアップロード方法については割愛します。詳しくは[Trailhead](https://trailhead.salesforce.com/ja/content/learn/modules/b2c-cartridges)、[Infocenter](https://documentation.b2c.commercecloud.salesforce.com/DOC1/topic/com.demandware.dochelp/content/b2c_commerce/topics/site_development/b2c_cartridges.html?resultof=%22%63%61%72%74%72%69%64%67%65%22%20)、[以前紹介したQIita記事](https://qiita.com/syunyama/items/21c71ac9c49b332e126d#1-%E3%82%AB%E3%83%BC%E3%83%88%E3%83%AA%E3%83%83%E3%82%B8%E3%81%AE%E4%BD%9C%E6%88%90) をご参照ください
:::

Hooksを利用するのにカートリッジに内には最低でも下記３つのファイルが必要です。
下記の例はSCAPIでGET `/customers/{custimer id}`でリクエストしたときにレスポンスボディの `c_test` というプロパティに「Sample Test」という文言がはいる実装です。





```json:package.json
{
    "hooks": "./hooks.json"
}
```

```json:hooks.json
{
    "hooks": [
        {
            "name": "dw.ocapi.shop.customer.modifyGETResponse",
            "script": "~/cartridge/scripts/hooks/sampleSCAPIHook"
        }
    ]
}
```


```js:cartridge/scripts/hooks/sampleSCAPIHook.js
exports.modifyGETResponse = function (customer, customerResponse) {
    customerResponse.c_test = "Sample Test"
};
```
:::note info
※ JSファイル名とフォルダは任意ですが、Hooksは大抵 `cartridge/scripts/hooks`フォルダ に入っていることが多いです。
※ SCAPI/OCAPI Hooksで 「modify*Response」というHook名でレスポンスに新しいプロパティを追加する際は **「c_」で始まるプロパティ名にする必要** があります。
:::

**実行結果：**
![スクリーンショット 2022-07-27 12.03.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/4ef4b022-748f-0c7b-d996-720287fdc5f3.png)


**フォルダ構造：**
![スクリーンショット 2022-07-27 10.52.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/db04698b-c57a-f571-ceea-c7071ac189ac.png)





## Custom Hooksの作り方・使い方

Custom Hooksも同様にカートリッジ内に実装していきます。
下記の例はコントローラ内部でHookを呼んでそのレスポンスをJSONとしてブラウザへ返しています。


```json:package.json
{
    "hooks": "./hooks.json"
}
```

任意のHook名を設定できます。

```json:hooks.json
{
    "hooks": [
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHook"
        }
    ]
}
```


```js:cartridge/scripts/hooks/sampleHook.js
exports.SampleHookOne = function(data) {
    return data + " sample_base"
}
```


Hookを呼ぶときは `dw.system.HookMgr.callHook`でHook名（sample.hook）とメソッド名（SampleHookOne）を指定します。

```js:cartridge/controllers/SampleHooks.js
"use strict";
var server = require("server");

server.get("Show", function (req, res, next) {
    var resultOne = dw.system.HookMgr.callHook("sample.hook", "SampleHookOne", "This is Data to Hook One");
    res.json({ one: resultOne });
    next();
});

module.exports = server.exports();
```


![スクリーンショット 2022-07-27 10.47.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/99932b2d-74c1-4fee-c78a-38a0183fcb90.png)



## 同じHook名に複数のスクリプトが設定されている場合

同じHook名に複数のスクリプトを設定することができますが、その際の挙動を紹介します。

### 同一カートリッジ内での挙動

例えば同一カートリッジ内に下記のように同じHook名に２つスクリプトを設定するとします。


```json:hooks.json
{
    "hooks": [
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHook"
        },
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHookTwo"
        }
    ]
}
```

このときの挙動はリストの上から順に実行されます_ので、この場合は、**sampleHook → sampleHookTwo** という順番で実行されます。


### 複数カートリッジに設定した場合の挙動

例えば、sample_base, sample_custom_one. sample_custom_two という３つのカートリッジそれぞれに  `sample.hook` というHook名にスクリプトを設定した場合、実行される順番はカートリッジパスの **左から** になります。
例えば、`sample_custom_two:sample_custom_one:sample_base` のような順番で設定した場合は左から順に **sample_custom_two → sample_custom_one → sample_base** の順番で実行されます。


### 返り値について

同じHook名に複数のスクリプトが設定されている場合下記のように実行した場合、resultに代入される返り値はどのようになるでしょうか？

```js
var result = dw.system.HookMgr.callHook("sample.hook", "SampleHook");
```

答えは、**最後に実行されたスクリプトの値**となります。
先の「同一カートリッジ内での挙動」の例で言うと、`sampleHookTwo.js` 内の `SampleHook` メソッドの返り値となります。

:::note warn
※ 注意点として、`sampleHookTwo.js`に`SampleHook` メソッドが設定されていなかった場合は、`sampleHook.js` の返り値ではなく、何も返ってきませんのでご留意ください。複数カートリッジに設定した場合も同様に全てのカートリッジの同じHook名に設定されたメソッドで最後に実行されたスクリプトの結果が帰ってくるようなので、最後のスクリプトにメソッド名がなかった場合は何も返ってきません。
:::



### まとめ

同一カートリッジ内での挙動と複数カートリッジ内の挙動をまとめます。
例えば下記のようなHooksの設定になっているとします。

**sample_custom_two カートリッジ**

```json:hooks.json
{
    "hooks": [
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHookCustomTwo"
        },
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHookTwo"
        }
    ]
}
```


**sample_custom_one  カートリッジ**


```json:hooks.json
{
    "hooks": [
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHook"
        },
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleCustomHook"
        }
    ]
}
```


**sample_base  カートリッジ**


```json:hooks.json
{
    "hooks": [
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHook"
        },
        {
            "name": "sample.hook",
            "script": "~/cartridge/scripts/hooks/sampleHookTwo"
        }
    ]
}
```


**呼び出し**

```js
var result = dw.system.HookMgr.callHook("sample.hook", "SampleHook");
```



さらにカートリッジパスが、`sample_custom_two:sample_custom_one:sample_base`となっている場合、下記の順番で実行されます。

1. **sample_custom_two**カートリッジの**sampleHookCustomTwo.js**内の**SampleHook**メソッド
2. **sample_custom_two**カートリッジの**sampleHookTwo.js**内の**SampleHook**メソッド
3. **sample_custom_one**カートリッジの**sampleHook.js**内の**SampleHook**メソッド
4. **sample_custom_one**カートリッジの**sampleCustomHook.js**内の**SampleHook**メソッド
5. **sample_base**カートリッジの**sampleHook.js**内の**SampleHook**メソッド
6. **sample_base**カートリッジの**sampleHookTwo.js**内の**SampleHook**メソッド


そして、呼び出し時の返り値は「**sample_base**カートリッジの**sampleHookTwo.js**内の**SampleHook**メソッド」の値となります。また、sample_baseカートリッジのsampleHookTwo.js内に**SampleHookメソッドが設定されていない場合、何も返ってきません。**



## SCAPIを使うときの注意点

デフォルトではSCAPI HooksはOffになっているので、Business Managerで **管理 > グローバル環境設定 > 機能スイッチ** に遷移して、「Salesforce Commerce API リクエストでのフックの実行を有効にします。」にチェックを入れて適用ボタンをクリックする必要があります。詳細は[ヘルプ](https://developer.salesforce.com/docs/commerce/commerce-api/guide/extensibility_via_hooks.html)をご覧ください。

![スクリーンショット 2022-07-27 11.57.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/ff96f5b9-05e2-28a9-1e46-4e06c23731bd.png)