ServiceNowのスクリプトインクルード拡張を利用してカスタマイズの影響を最小化する

## はじめに

スクリプトインクルードは、サーバーサイドスクリプトをAPI化し、再利用可能な形で共有する仕組みです。ビジネスルールやUIアクションでもスクリプト実行はできますが、実行契機が限定されるこれらのスクリプトと違って、スクリプトインクルードはどこからでも呼び出せるので、より汎用化して共用することで、似た機能が複数のスクリプトに実装されることを防ぎ、メンテナンス性の高いシステムにすることができます。

当然OOTBでも広範に利用される仕組みであり、プラットフォームだけでなく、ストアアプリでも多くの機能がスクリプトインクルードによって実装されています。

ところで、スクリプトインクルードには「拡張」という仕組みがあるのをご存じでしょうか。この仕組みを活用すると、OOTBのアプリケーションを直接変更することなく、あるいは変更を最小限にして望ましい追加機能を持たせることができるようになります。今日はこのような、スクリプトインクルードの拡張による活用を考えてみたいと思います。

## スクリプトインクルードの拡張とは

ドキュメントでは直接言及されていませんが、Developerサイトのこの記事がわかりやすいと思います。

https://developer.servicenow.com/dev.do#!/learn/learning-plans/vancouver/new_to_servicenow/app_store_learnv2_scripting_vancouver_extend_a_script_include

ServiceNowのサーバーサイドJavaScriptには、`Object`のプロトタイプに`extendsObject`という関数が定義されています。これは標準的なJavaScriptにはないServiceNow特有の機能です。これを利用すると、既存のスクリプトインクルードのすべての機能を継承しながら、別のオブジェクトを定義することができます。

正直なところ、これによって実現するオブジェクト指向機能はそこまで強力なものではありませんので、過信は禁物[^1]なのですが、典型的に使われるのはなんといってもAjax関係です。OOTBの`AbstractAjaxProcessor`を拡張して、自分のAjaxサーバーサイド処理を記述するというのは、多くのユーザーにとって最も馴染みのあるスクリプトインクルード拡張だと思います。

``` JavaScript: QiitaAjax (Script Include)
var QiitaAjax = Class.create();
QiitaAjax.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
    /**
     * Return the system ID of the manager of given user.
     */
    getManager: function() {
        const userId = this.getParameter('sysparm_userid');
        if (GlideStringUtil.isEligibleSysID(userId)) {
            return new global.GlideQuery('sys_user')
                .withAcls()
                .get(userId, ['manager'])
                .orElse({
                    manager: ''
                })
                .manager;
        }
        return '';
    },
    type: 'QiitaAjax'
});
```
このスクリプトインクルードを用意したうえで、アサイン先フィールドに対してonChangeで動作する以下のクライアントスクリプトを組み合わせると、アサイン先フィールドに設定したユーザーのマネージャーをレビューアーのフィールドに設定することができます。

``` JavaScript: Add manager as a reviewer (Client Script)
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue === '') {
        return;
    }
    var ajax = new GlideAjax('x_snc_qiita_sample.QiitaAjax');
    ajax.addParam('sysparm_name', 'getManager');
    ajax.addParam('sysparm_userid', newValue);

    ajax.getXMLAnswer(function(answer) {
        g_form.setValue('reviewer', answer);
    });
}
```
動画でなくて恐縮ですが、イメージとしてはこんな形になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/0ba6d5ca-dd1c-4cb5-548e-be41153bd5b5.png)

この辺のお作法は以下のドキュメントが詳しいので掘り下げません。

https://developer.servicenow.com/dev.do#!/reference/api/utah/server/no-namespace/GlideStringUtilScopedAPI#GSUS-isEligibleSysID_S?navFilter=stringutil

ところで、Ajaxのスクリプトインクルードといえば、**クライアント呼び出し可能なスクリプトインクルード (Client Callable Script Include)** という種類のスクリプトですが、これはこれでまたいろいろ考えるポイントがあるテーマなので、別の機会に語りたいと思います。[^2]

## OOTBスクリプトインクルードの拡張

この一連の投稿では、OOTBという4文字略語を断りなく頻繁に使っていますが、Out-of-the-boxの略で、ServiceNowでは極めて頻繁に出てくる用語です。意味の説明はこのWikipediaの記事がわかりやすいです。要するに製品として出荷された状態のことです。

https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%A6%E3%83%88%E3%83%BB%E3%82%AA%E3%83%96%E3%83%BB%E3%82%B6%E3%83%BB%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9

### OOTBビジネスロジックの変更

それはさておき、OOTBのビジネスロジックのうち、一部を変更したい、ということはあると思います。セットする値が決め打ちになっているところを、別の部分を参照するように変えたいだとか、条件に応じて自動生成するレコードの内容を変更するようにしたいだとかというときです。

今回、例えば、OOTBの処理はUIアクション（ボタン）を押すことで起動し、裏でスクリプトインクルードを呼び出しているものとしましょう。わざわざ図にするほどもない話ですが、シーケンス図的に示すとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/e93914bd-2da8-cf3a-a1ee-95a51db1e029.png)

### 拡張によるロジックの変更

こういうとき、拡張機能を使って、別のスクリプトインクルードに元の処理を継承させて、差分だけ追加開発することで、OOTBに手を触れることなく、欲しい機能を実現することができます。概念的には下図のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/a2198750-d6cd-4e71-7b06-bdbccbcc5f1a.png)

### 変更が多くなりそうなんですが

もしかすると、このような実装方法は、OOTBのプログラムのコピーを元にしたカスタムバージョンを作ることなので、新しいスクリプトインクルードが必要になるばかりか、呼び出し元のUIアクションやビジネスルール、あるいはスクリプトアクションを変更しなくてはならなくなるではないかと思われたかも知れません。それだったら、むしろ呼び出し先のスクリプトインクルードの一部だけを変更して、そのほかに手を触れないほうがよいのではないかという主張です。

こういう感じですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/241a7723-c0db-1f5b-d796-f52f96b4341a.png)

これも、変更箇所を最少にすると意味では一理あるのですが、個人的にはあまりおすすめできません。スクリプトインクルードは業務ロジックの中核を占める多くの処理がまとめて書かれているので、製品のアップデートでも変更が入りやすい部分です。ここにカスタマイズを入れてしまうと、アップデート時に製品のアップデートとカスタマイズの競合が起きやすくなります。[^3]

また、スクリプトインクルードへの変更は影響範囲を特定するのが難しいのも大きな問題です。UIアクションやビジネスルールは基本的によそからは呼ばれないので、変更しても他の機能への影響を心配する必要性があまりありません。しかし、スクリプトインクルードは広く公開されたAPIなので影響範囲は大きく、しかも将来、別の製品機能から使われるようにならないとも限りません。スクリプトインクルードそのものに手を入れると将来まで頭痛の種を抱えることになります。

仮に競合が発生したとしても、OOTBのUIアクションやビジネスルールは総じて処理が少なく、アップグレードと競合しても解決は難しくありません。スクリプトインクルードはメソッド単位でもしばしば長大なので、処理を変更すると、その時のナレッジをよほど注意深く文書化しておかないと、将来の製品アップデートとのマージは容易ではありません。

## まとめ

というわけで、スクリプトインクルードの拡張という話題から始めましたが、本題としてはOOTB処理にカスタマイズを入れたいときに、どのような変更の仕方をするかということで、スクリプトインクルードそのものに手を入れることは極力避け、既存のスクリプトインクルードを拡張して必要なカスタマイズを行った新しいスクリプトインクルードを作成し、それを呼び出すように呼び出し元を変更するというのが、変更箇所が多いように見えて、実は将来にわたって堅牢なのではないかというお勧めになります。



[^1]: 例えばデザインパターンをスクリプトインクルードでやろうとしても、難しいわりにありがたみがないものになりがちです。OOTBにもそれっぽいものが見受けられますが……
[^2]: 上のコードでいうと、スクリプトインクルードのほうの「`.withAcls()`」という呼び出しの部分がその話題に関係しています。
[^3]: また、単純にユーザーによる変更が許可されていないスクリプトインクルードもたくさんあり、変更したくても変更できないこともよくあります。これは副次的な言い訳ですが。