Salesforce B2C Commerceのコントローラを修正・拡張する(append, prepend, replace, ミドルウェア)


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


コントローラを修正・拡張する際の方法を紹介します。SFRAの基礎となる`app_storefront_base`を拡張したいときや自作で作ったカートリッジを修正・拡張したいときに便利です。


## 今回覚えて欲しいこと

1. カートリッジはカートリッジパスの左が優先
2. append, prepend, replaceのそれぞれの役割
3. コントローラのミドルウェアは左のファンクションから実行される



## （準備）まずはサンプルに使うコントローラとテンプレートを作る


２つの名前のカートリッジを作成します。カートリッジの作成方法は[こちら](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)。

* controller_sample_base（ベース）
* controller_sample_custom (カスタム)


ベースとしてcontroller_sample_baseに `Test.js` と言うコントローラを作り、`Show`という名前のルートを作ります。
`Show`ルートは `controller_sample_base` カートリッジ内の `templates/default/sample/test.isml` をテンプレートとしてブラウザに描画します。
さらにcontroller_sample_customに同様の`Test.js`と言うコントローラを作り、controller_sample_baseの`Test.js`を上書きしながらappend, prepend, replaceの動きを確認していきます。
作成したフォルダ構成は下記の画像の通りです。
![image (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/991d9484-550e-87cc-7fa3-93871d76115f.png)



### ベースに利用するコードとテンプレート

```js:controller_sample_base/cartridge/controller/Test.js
'use strict';

var server = require('server');

server.get('Show', function (req, res, next) {
    res.render('sample/test', {
        param1: "Test",
    }); 
    next();
});

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

```xml:controller_sample_base/cartridge/templates/default/sample/test.isml
 <html>
 <body>
   <h1>${pdict.param1}</h1>
   <h1>${pdict.param2}</h1>
   <h1>${pdict.param3}</h1>
 </body>
 </html>
```

### ベースのみで実行した場合

ベースのみのカートリッジで「[https://your-env.commercecloud.salesforce.com/on/demandware.store/Sites-YourSite-Site/ja_JP/Test-Show](https://zzjs-021.sandbox.us02.dx.commercecloud.salesforce.com/on/demandware.store/Sites-RefArch-Site/ja_JP/Test-Show)」のようにブラウザからアクセスした場合このような見た目になります。

![image (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/fad7a1ed-929b-0d86-44e7-46e4b087cb94.png)




## 1.カートリッジはカートリッジパスの左が優先

カートリッジパスは左が優先になります。
関連するドキュメントとして[Info Center](https://documentation.b2c.commercecloud.salesforce.com/DOC1/topic/com.demandware.dochelp/content/b2c_commerce/topics/sfra/b2c_sfra_modules.html?resultof=%22%61%70%70%65%6e%64%22%20)、[Qiita](https://qiita.com/syunyama/items/21c71ac9c49b332e126d#4%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%97%E3%81%9F%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E3%82%A2%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E3%81%AB%E3%81%99%E3%82%8B)があります。
今回は作成したカートリッジを優先させた上でさらにcustom→baseと言うようにカスタムが優先となるようにカートリッジパスを設定します。

カートリッジパス：
`controller_sample_custom:controller_sample_base:その他のカートリッジ...`

これで、カスタムカートリッジでTest.jsを上書くor拡張することができます。
例えば、`controller_sample_base/cartridge/controller/Test.js`
のコードを`controller_sample_custom/cartridge/controller/Test.js`へコピペして、`"Test"`を
`"Test2"`へ書き換えた場合、`controller_sample_custom`の`Test.js`が優先されるため画像のような見た目になります。(**ただし本当にベースを完全に置き換えたい場合は、このやり方ではカスタムの内容がベースのような意味合いになるので、ルートを置き換える場合は、後述するreplaceをお勧めします。**)
![image (13).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/98897cc6-ce3a-f277-6ab9-cde34a73d090.png)



#### Tips

今回はコントローラの上書きの例でしたが、他にもscripts配下やtempletes配下のファイルも同じディレクトリでかつ同じ名前の場合はカートリッジパスの左側にあるカートリッジのファイルが優先されます。



## 2.append, prepend, replaceのそれぞれの役割

append, prependはベースカートリッジの挙動を活かしつつ、処理をしたいときに使い、 replaceは文字通りルートを丸っと置き換えたい場合に利用します。ルートの処理はprepend→(get/post/use)→appendの順で処理が走ります。そのため、既存のルートの前に処理を挟みたい場合はprepend、後に挟みたい場合はappendとなります。
※[get/post/useの違い](https://documentation.b2c.commercecloud.salesforce.com/DOC1/topic/com.demandware.dochelp/sfrajsdoc/js/server/Server.html)


### append, prependの挙動の確認

実際にappend, prependを使ってみて挙動を確認してみましょう。
下記に実験するコードを記載します。


```js:ベースのTest.jsのコード
==省略==
server.get('Show', function (req, res, next) {
    res.render('sample/test', {
        param1: "Test from get",
        param2: "Test from get",
        param3: "Test from get",
    }); 
    next();
});
==省略==
```


※ append, prepend, replaceを利用する場合は `server.extend(module.superModule)`を呼ぶ必要がありますのでご注意ください。

```js:カスタムのTest.jsのコード
"use strict";

var server = require("server");
server.extend(module.superModule);

server.prepend('Show',function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param2 = 'Test from prepend';
    res.setViewData(viewData);
    next();
});

server.append("Show", function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param3 = "Test from append";
    res.setViewData(viewData);
    next();
});

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


上記のコードを実行した場合、このような結果になります。

![image (14).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/7e53a232-e5ec-1725-5c32-89e58589b797.png)



なぜこのような結果になったかというと、このような順序で処理が行われたからです。

**(1) prependの実行**
param1　→　null
param2　→　Test from prepend
param3　→　null

**(2) getの実行**
param1　→　Test from get
param2　→　Test from prepend をTest from getで上書き
param3　→　Test from get

**(3) appendの実行**
param1　→　Test from get
param2　→　Test from get
param3　→　Test from getをTest from appendで上書き



### replaceの挙動の確認

上記の「append, prependの挙動の確認」で使用したベースのTest.jsのコードに対して下記のカスタムコードを利用します。


```js:カスタムのTest.jsのコード
"use strict";

var server = require("server");
server.extend(module.superModule);

server.replace('Show', function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = 'Test from replace';
    res.setViewData(viewData);
    res.render('sample/test');
    next();
});

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


このカスタムコードを実行した場合、画像のようになります。

![image (15).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/593265c3-ba91-ff4c-0aee-90f0be9b1a4d.png)


なぜこのような結果になったかというと、ベースのTest.jsのShowは全く実行されず、カスタムのTest.jsのShowが実行されたためです。

:::note warn
replace はベースが get や post だったとしても置き換えるため、結果的に  get と post どっちでもいいようなルートになるのでご注意ください。get のみに制限したい場合は、下記のヘルプのようにミドルウェアを利用します。 
https://github.com/SalesforceCommerceCloud/storefront-reference-architecture/blob/master/cartridges/modules/server/README.md#replacing-a-route
:::



### 追記 2022/6/8
#### カートリッジが3つ以上の場合の挙動も記載しておきます。
例えば、このようなカートリッジパスがあり、それぞれのカスタムカートリッジにappendもprependもあった場合、
`controller_sample_custom1:controller_sample_custom2:controller_sample_base`
下記のように１から順番に実行されるようです。
1. custom1のprepend
1. custom2のprepend
1. baseの(get/post/use)
1. custom2のappend
1. custom1のappend



#### replaceを途中で入れた場合も記載しておきます。
例えば、このようなカートリッジパスがあり、カスタムカートリッジにappendもprependもあった場合、
`controller_sample_custom:controller_sample_replace:controller_sample_base`
下記のように１から順番に実行されるようです。
1. customのprepend
1. replaceの(get/post/use)
1. customのappend

つまり、baseは置き換えられ、完全に実行されなくなります。








## 3.コントローラのミドルウェアは左のファンクションから実行される

コントローラには本処理以外の処理を挟む事もできます。それをミドルウェアと呼んでいます。

ミドルウェアを作成する。

```js:controller_sample_base/cartridge/scripts/middleware/sample.js
'use strict';

function sampleOne(req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = viewData.param1 + ' :Test from middleware sampleOne';
    res.setViewData(viewData);
    next();
}

function sampleTwo(req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = viewData.param1 + ' :Test from middleware sampleTwo';
    res.setViewData(viewData);
    next();
}

module.exports = {
    sampleOne: sampleOne,
    sampleTwo: sampleTwo,
};
```

ミドルウェアをコントローラに設定。

```js:controller_sample_base/cartridge/controller/Test.js
'use strict';

var server = require('server');
var sample = require('~/cartridge/scripts/middleware/sample');

server.get('Show', sample.sampleOne, sample.sampleTwo, function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = viewData.param1 + ' :Test';
    res.setViewData(viewData);
    res.render('sample/test'); 
    next();
});

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

これらを実行すると、画像のような内容になります。
![image (16).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/63aa86a7-94e9-483a-1294-18cd0cd8482f.png)



なぜこうなるかというと、このような順序で処理が行われたからです。

**(1) sampleOneの実行**
param1　→　undifined + :Test from middleware sampleOne

**(2) sampleTwoの実行**
param1　→　param1 + :Test from middleware sampleTwo

**(3) 本処理の実行**
param1　→　param1 + :Test

こちらの[ヘルプ](https://documentation.b2c.commercecloud.salesforce.com/DOC1/index.jsp?topic=%2Fcom.demandware.dochelp%2Fsfrajsdoc%2Fjs%2Fserver%2FServer.html)を見たらわかるのですが、 
`get(name : string, arguments : Array.<function()>)`
なので、左から設定されているファンクションが順に実行されます。
そのため、例えば、

```js:Test.js
server.get('Show', sample.sampleOne, function (req, res, next) {
    ==省略本処理==
}, sample.sampleTwo);
```

みたいな感じで、**sampleOneの実行 → 本処理の実行 → sampleTwoの実行**のように順番を変えることも可能です。



**Tips**
外部のモジュールをインポートする際に利用している `~`は自ファイルが所属しているカートリッジのみを探索します。その他のカートリッジ内のモジュールを探したい時は `*`を利用します。
今回の例で言うと、
`var sample = require('~/cartridge/scripts/middleware/sample')`
または、
`var sample = require('*/cartridge/scripts/middleware/sample')`






## まとめ

append, prependとミドルウェアを合わせた場合の処理の順番を記載します。

1. prependに設定された左からの関数が順に実行される
2. get/post/useに設定された左からの関数が順に実行される
3. appendに設定された左からの関数が順に実行される


ミドルウェアがあるからappendとかprependいらないんじゃ？と思う方いらっしゃるかも知れませんが、SFRAの作りとしては***ベースのコードは修正せずにカスタムして行った方がいい***です。
appendやprependは***ベースとなっているコードを一切触らず***に処理を追加できるので非常に便利です。