#はじめに
この文章では ThingWorx を使用したウェブアプリケーションの開発にあたって、アンチパターンともいえる「なるべく避けたい実装」を解説します。
対象読者
ThingWorx 8.x 以降の開発経験のある人を対象としてます。
対象バージョン
ThingWorx 8.x 以降
参照すべきリソース
上記、Application Development Guide は一読を強くお勧めします。
Thingモデル編
##システム標準の ThingTemplate から Thing が作られている
ThingWorx はシステム標準として、下記のような ThingTemplate を持っています。
- GenericThing
- RemoteThing
- Database
- FileRepository
よく使われるのは上記の ThingTemplate ですが、そのほかにもたくさんの ThingTemplate が ThingWorx のインストール時に作成されます。
こうした ThingTemplate から直接 Thing を生成するのは避けるべきです。理由は簡単で、システム標準の ThingTemplate には、後から ThingShape を追加することができないからです。
複数の Thing に対して共通のプロパティやサービスを付加する際には、それらを定義した ThingShape を ThingTemplate に追加することで容易に実現できますが、システム標準の ThingTemplate から直接 Thing を生成してしまうと、こうした ThingShape の恩恵を受けられません。
##ThingTemplate にプロパティやサービスを定義している
可能であれば、プロパティやサービスは Thing や ThingTemplate ではなく ThingShape で定義するのが望ましいです。
Thing に直接プロパティやサービスを追加すると、他の同様の Thing を作らなければいけなくなった時に、毎回必要なプロパティとサービスを Thing ごとに定義しなければならなくなります。一度定義したプロパティやサービスを他の Thing 生成時にも使いまわせるようにするためには、こうした共通プロパティは ThingTemplate から継承されるべきです。確実にその Thing にしか存在しないとわかっているプロパティやサービス、もしくはその Thing にしか存在を許されないプロパティやサービスがある場合には、この限りではありません。
ThingTemplate にプロパティやサービスを定義する際には、本当にそれが ThingTemplate で定義されるべきなのかを慎重に見極める必要があります。なぜなら、Thing は生成された後にそのベースとなった ThingTemplate を変更できないからです。別々の ThingTemplate から生成されたふたつの Thing が共通のプロパティを必要とした際、同じプロパティをそれぞれの ThingTemplate に定義するといった重複が発生します。たとえばプロパティのデフォルト値やベースタイプ(BaseType: ThingWorx でのデータ型)を変更する際、そのプロパティを定義した ThingTemplate のすべてで作業をおこなう必要が出てきます。
ThingShape はプロパティとサービスを定義するのに最も適した場所です。ThingShape は Thing のモデルを考える際に以下の二つの重要な特徴を備えています。
- ThingShape は Thing にも ThingTemplate にも付加できる。
- ThingShape の付加は、ThingTemplate や Thing の生成時だけでなく、あとからいつでもできる。
ThingTemplate も Thing も、好きな時に好きな数の ThingShape を追加できるので、ThingShape を効果的に使うことでプロパティやサービスの定義の再利用性を高められます。
ThingTemplate に定義をすべき時
とはいえ、すべての場合において ThingShape がプロパティやサービスの定義場所として最適かというと、そうでもありません。とくにサービスに関しては、どうしても ThingTemplate に定義しなければならない場合があります。それは、ふたつ以上の ThingShape のプロパティを使って処理を行うサービスを実装しなければならない時です。ThingShape に実装するサービスは、その ThingShape で定義されているプロパティへのアクセスだけが保証されます。次のケースを考えてみましょう。
- ThingShape A にプロパティ A が存在。
- ThingShape B にプロパティ B が存在。
- ThingTemplate X は ThingShape A と B の両方を持つ
- ThingTemplate Y は ThingShape A のみを持つ。
ここで、ThingTemplate X から Thing X を生成します。Thing X はプロパティ A と B を両方持ちます。そして ThingTemplate Y から Thing Y を生成します。すると、Thing Y にはプロパテx A は存在しますが、B は存在しません。このとき、もしも ThingShape A に次のサービスを書くと何が起こるでしょうか?
result = A / B;
結果は B が undefined のためエラーが帰ります。もちろん、
result = me.B ? A / B : A;
などとして、Thing に B が存在するかどうかをチェックしてエラーが発生しないようにできますが、依然として値は求められないままです。
こうしたときには、ThingTemplate X にこのサービスを定義する方がシンプルに解決できます。
##余談: ThingTemplate のサービスはクラスメソッドのようななにか... ではない
ThingTemplate と Thing の関係は、よく雛形と実体として例えられます。ThingWorx は Java で動作しますから、Java 風にいうならクラスとインスタンスに相当するでしょうか。
ところで、ThingTemplate にはサービスを定義できますが、このサービス、実は ThingTemplate 単体では実行できません。Java であれば Static として定義されているいわゆるクラスメソッドは、そのクラスをインスタンス化しなくても実行できます。しかし、ThingTemplate でサービスを定義すると、そのサービスは必ず「実行する Thing を指定」する必要があります。
具体的には、ThingTemplate で実装したサービスをマッシュアップビルダーで呼び出す際には、かならず動的サービスとして呼び出さなければなりません。(ThingTemplate の名前の右隣にある "Dyanamic" をチェックします。画像参照)。
動的サービスは Thing 名を必須パラメタとしますから、たとえば Java でやるように「よく使うユーティリティ的なメソッドはクラスメソッドにして」といったノリで ThingTemplate にサービスを定義しても、意図した通りには動作しません。結局は、かならず一つは Thing が必要になるのですから。
なお、この制限があるのは、ThingTemplate にユーザーが追加したサービスのみです。ThingTemplate が最初から持っているサービス、たとえば GetImplementingThing といったサービスはもちろん ThingTemplate から直接呼び出せます。
##ユーティリティ ThingShape を使っていない
Thing のサービス実行時にログを出力するのはよくあることですが、その際に業務要件として「どの Thing のどのサービスがログを出力したのかをメッセージに含める」ことが求められたとしましょう。これは、
var InfoLog = function(message) {logger.info(me.name + ": MyService: " + message);};
という関数を定義して、JavaScript の残りの部分から InfoLog() を呼び出すと簡単に実現できます。問題は、この関数定義をすべての Thing に反映しなければならなない、というところです。個別の ThingShape や ThingTemplate にこの関数定義を追加していくのはナンセンスですので、こうしたときは「よく使う関数」をまとめた「ユーティリティ ThingShape」を作成します。
ユーティリティ ThingShape では、上記の関数を次のように定義します。
var InfoLog = function(service, message) {logger.info(me.name + ": " + service + ": "+ message);};
このユーティリティ ThingShape を ThingTemplate に追加した後、呼び出すサービス側は次のようにします。
me.InfoLog("MyService", "file not found");
もしも、ログ出力の形式を変えたくなった場合には、ユーティリティ ThingShape の関数定義を1箇所変えるだけですみます。
##ロジックとデータを分離していない
Application Development Guide に詳細な記述がありますが、Thing を設計する際にはビジネスロジックを司る Thing とデータを保持する Thing を分離します。典型的な ThngWorx アプリケーションでは、次のような Thing が作られることになります。
- マッシュアップから呼び出され、マッシュアップからの入力を検査したりマッシュアップにデータを表示したりする User Interface Thing
- ビジネスロジックを担当する Manager Thing
- データベースに対する問い合わせを行う Database Conection Thing
ThingWorx は画面の描画を担当する Mashup と、Mashup へデータサービスの提供を担当する Thing や ThingTemplate と分離されていますが、さらに Thing を画面のコントロールを司るもの、ロジックを司るもの、そしてデータベースなどリソースへのアクセスを司るもの、に分離します。
筆者は小規模なアプリケーションを開発する際には、上記の User Interface Thing と Manager Thing をひとつの Thing にまとめることが多いです。そうした Thing には UIController という名前をつけています。一つの指針として、UIController に実装するカスタムのサービスが 20を超えてくるようなら、User Interface Thing と Manager Thing とに分離するようにしています。
Application Development Guide に書かれていないこととして、Manager Thing (ひとまとめにした場合は UIController Thing)をさらに、純粋なロジックとパラメータとに分離します。ロジックは Manager Thing に実装しますが、ユーザーごとに設定が変わるようなプロパティは実行時の環境を指定する専用の Thingを作ってそちらに保存します。これを Configuration Set Thing と呼んでいます。
たとえば、データベースへの問い合わせ結果を CSV に変換し、ファイルレポジトリに格納するアプリケーションを考えた時、それぞれの Thing の役割は次のようになります。
- User Interface Thing: マッシュアップに配置されたボタンのクリックイベントを拾い、一連の処理を実行する。
- Configuration Set Thing: どのデータベースに接続し、どのファイルレポジトリにファイルを格納するのかなど、実行時に決定される変数を提供する。
- Manager Thing: Database Connection Thing: の SQL サービスを呼び出し、CSV に変換し、ファイルレポジトリに格納する。データベースに接続するための Database Connector Thing やファイルリポジトリの名前は実行時に Configuration Set Thing から読み出す。
- Database Connection Thing: データベースに接続し、SQL を実行する。
ひとつのアプリケーションがひとつのシステム中で動作設定を変えながら複数実行されることはよくあることなので、動作設定をロジックから分離しておくことで、メンテナンス性が向上します。なお、Configuration Set Thing はユーザーごとに持つことが多くなりますが、実行時に Manager Thing の振る舞いを変更させるという性質上、実行時に動的に渡されなければなりません。具体的な方法としては、ユーザーのログイン ID に紐づけて User Interface Thing から呼び出したり、マッシュアップでユーザーに明示的に選択させたり、マッシュアップパラメータとして Configuration Set Thing の名前を渡したりします。
とても大切な指針として、User Interface Thing や Manager Thing には実行時の状態を保存してはいけないということを書き添えておきます。アプリケーションが同時に複数実行される場合でも、User Interface Thing や Manager Thing は一つしか実行されません。実行時の状態を保存するのには適しません。実行時の状態を保存したいときには、Configuration Set Thing を使うか、セッション・パラメータを使います。
##プロジェクトや名前づけルールを使っていない
ThingWorx では簡単にマッシュアップが作成できることもあり、運用を初めてしばらく経つと非常にたくさんの Thing やマッシュアップが作られることになります。Thing やマッシュアップを「エンティティ」と呼びますが、エンティティを効率的に管理するためにプロジェクトを使用します。ThingWox 8.3 以降はエンティティのエクスポートは Project 単位で実施できるようになっています。
また、名前づけルールは非常に大切です。ThingWorx は REST API で動作するよう設計されているため、エンティティの名前は「同一のエンティティ種類内において」ユニークでなければなりません。エンティティの名前の重複を防ぐために、接頭辞や接尾辞を使うようにします。
- 接頭辞にはプロジェクトの短縮名など、エンティティが属する集合体の名前を二文字から三文字程度でつける。
- 接尾辞にはエンティティの種類を示す文字を二文字程度でつける。
たとえば、遠隔監視をおこなう RemoteMonitor というプロジェクトを作成したなら、そのプロジェクトに属するエンティティの名前は "RM." から始めるようにします。"RM.UIController" とか "RM.Manager" などですね。また、Configuration Set は複数作られることになりますから、"RM.ConfigSet.Kanoloa" などの名前をつけます。"Kanoloa" はここではログイン ID です。
Thing と Mashup には接頭辞はつけても接尾辞はつけないのが一般的です。それ以外のエンティティに関しては、下表を一例としてください。
エンティティ種別 | 接尾辞 | 備考 |
---|---|---|
ThingShape | .TS | |
ThingTemplate | .TT | |
Thing | なし | |
DataBase Thing | .DB | |
FileRepository | .RP | |
MediaEntity | .Img, .Iconなど | 目的ごとに簡潔に |
Style Definition | .Style | |
State Definition | .State | |
Mashup | なし | |
Network | .NW |
セッションパラメータを使っている
サービスが戻した INFOTABLE の選択行を複数のマッシュアップで同期させたいときがあります。こうした「複数のマッシュアップ間で実行時の状態を受け渡す」目的に使うのがセッション・パラメータです。セッションパラメータは非常に便利ですが、使いすぎに注意します。とうのも、セッションパラメータはアプリケーションごとに指定できません。そのシステムで定義されているセッションパラメータは、必ず全てがブラウザに渡されます。システム全体でセッションパラメータが多くて10程度であれば管理も容易ですが、数100のセッションパラメータが存在するとブラウザのメモリも消費しますし、管理も大変です。
また、アプリケーションを別の ThingWorx インスタンスに移動させる際、セッションパラメータはエンティティのインポートだけでは設定されず、かならずコンポーザーから ThingShape を追加する操作を行う必要があります。これは頻繁に「忘れてしまう」動作で、結果としてアプリケーションんが正常に動作しない初期不良のよくある原因となっています。
GetImplemetingThingsWithData() サービス
ThingTemplate には GetImplemetingThings() と GetImplementingThignsWithData() というよく似た名前の二つのサービスがあります。違いは単純で、前者の GetImplementingThings() サービスは RootEntityList データシェイプの INFOTABLE を返します。つまり isSystemObject, name, description, tag, homeMashup, avatar が戻されます。Grid ウィジェットや List ウィジェットに Thing の一覧を表示する際、RootEntityList の内容では足りないことがあります。具体的には、Thing が実際に持っているプロパティの値を一緒に表示したくなります。その際には、後者の GetImplementingThingsWithData() サービスを使います。
GetImplementingThingsWithData() サービスについては、三つのことを知っておく必要があります。
- GetImplementingThingsWithData() を呼び出した ThingTemplate がもつプロパティの値しか参照できない。その ThingTemplate を継承した Thing が独自に持つ ThingShape や、さらに別の ThingTemplate が作成されている場合には、下流で定義・追加されたプロパティの値は渡されない。
- 一方で、その ThingTemplate を継承するすべての Thing の情報が GetImplementingThingsWithData() の戻り値として渡される。数100の Thing がある場合、ブラウザのメモリの消費に注意する必要がある。
- Thing の GetProperties()サービスと違い、"Automatically Update When Able" 機能が使えない。
最後の一つはあまり知られていないことですが、GetProperties() サービスのみを使っている場合には、Auto Refresh ウィジェットによる定期的な画面際描画は必ずしも必要ではありません。GetProperties() サービスは特殊な作りとなっており、ブラウザと ThingWorx サーバーが WebSocket で接続している場合には、プロパティの更新がブラウザに伝えられ、ウィジェットの表示が自動的に変わります。GetImplementingThingsWithData() でプロパティの値を取得した場合、この機構は動作しません。
#おわりに
冒頭で書きましたが、ThingWorx Application Development Guide には目を通すことを強く推奨します。最新の 8.5 であれば、日本語版も提供されています。この記事は、ThingWorx Application Development Guide の補足記事として捉えていただけると幸いです。