制約のある状況での開発プロセスについて、MVCフレームワーク、FileMakerでそれぞれ簡単ですが、設計をやってみました。そして、本題であるINTER-Mediatorでの設計を今回は説明します。
まず、以下の図は、共通の設計モデルを示すクラス図に、INTER-Mediatorで考慮すべきことをメモとして追加したものです。まず、CategoryModelに紫色のメモで示したように、カテゴリーの全リストはINTER-Mediatorでは不要なので、ここメソッドを削除しています。また、PickingUpクラスやMemoListクラスの属性は、結果的にデータベースのテーブルにあるフィールドが必要になるので、ここではどんなデータがUIのレイヤーに来るのかを明示するため、属性にフィールド名を追加しました。
まず、前の図の下の方にあるステレオタイプがtableの部分をみてください。これは、ドメインモデリングの作業で作ったものと同じであり、もちろん、このままデータベースのスキーマとして定義します。アクセス権の設定なども行、データベースの準備は進めておきます。そして、ステレオタイプがmodelのものは、「データベースから何が抽出されたいか」を操作で示しています。これらの機能を、この後の定義ファイルの設計において、コンテキストとして取り出せるものを用意する段階で改めて調べます。
そして、前の図の上半分に記述されたUIレイヤーについて検討を進めます。まず、UI Componentステレオタイプの2種類のものは、INTER-Mediatorでは実装された機能やプラグインとして用意されているので、それらを呼び出すだけでOKです。ここは機能そのものやプラグインの制限が影響なければ特段の配慮は通常は不要です。
前の図で、緑色に塗ったクラスは、結果的にはある段階で、エンクロージャーとして展開可能なものと対応づけるようにします。INTER-Mediatorでの設計のポイントは、エンクロージャーとリピーターの特定、つまり、どこでこれらの要素を出すのかによります。ここでは設計段階なので、まずは、データベースから得られる1つのリレーションに対して、UIレイヤーでは1つのクラスとして認識するところまでを進めます。つまり、メモの一覧がデータベースにアクセスしてリレーションとして得られます。リレーションなので複数のレコードですが、そのレコードには、緑色のクラスの属性にあるようなフィールドが存在するはずです。その意味で、緑色のクラスがリレーションかあるいはエンクロージャーを示すのであれば、その属性というのは途中をすっ飛ばした不正確な記述になるかと思いますが、ここでは勘弁性を優先してこのように記述します。この記述については、定義ファイルの設計でも同様な考慮をします。
MemoListクラスは、データベースから得られたメモのリストを展開するという意味ではわかりやすいでしょうけども、PickingUpは、大分類と小分類が入り乱れます。一応、1つのクラスPickingUpとして記述しましたが、それは正しいでしょうか? ですが、まずはカテゴリを取り出してボタンと一緒に表示するという仕組みが必要であることを表明するためとしてPickingUpクラスの存在は無駄なものではありません。しかしながら、大分類/小分類というかなりややこしい状況があります。
ここから、HTMLのテンプレートを実際に作ります。もちろん、これはINTER-Mediator用語で言えばページファイルを作成に入ります。ここで解決すべきことは、エンクロージャーとリピーターの特定になります。もちろん、要素の取り出しも重要ですが、INTER-Mediatorではこの「エンクロージャーとリピーターの特定」さえできれば、モデリングは終わったも同然だと考えられます。ただ、今回は設計プロセスを追うのが趣旨なので、詳細に記述します。
エンクロージャーとリピーターの特定に必要なことは、得られるリレーション間の関連です、つまり、リレーションシップの特定に他なりません。まず、前のシステムデザインで記述されたMemoListについては、メモが複数あるので、リスト全体とメモ単体の間に1対多の関係があり、ここで1に対してエンクロージャー、多に対してリピータを割り当てれば良いので、ここでのエンクロージャーとリピーターの識別は容易です。なお、結果的に1:*になる部分は*の数が一定しない、つまりデータに応じて変わる部分であり、結果的にデータベースからのリレーションを展開することになるでしょう。ボタンが10個並ぶなら、1:10になるので、これらはスタティックなものとも言えます。つまり、HTMLのテンプレートをモデリングした場合に出てきた1:*の部分がエンクロージャーとリピーターとして記述すべき箇所になるということが言えます。
しかしながら、ここで、ステレオタイプがtableの部分をみてください。memoテーブルのtop_category_id、sub_category_idは、それぞれcategoryテーブルのcategory_idと対応づけていて、ここで各メモがどのカテゴリーに分類されているのかを記録できるようになっています。また、category同士のリレーションシップも設定されており、これにより、大分類と小分類のレコードの関連づけが行われています。このようなスキーマにみられるリレーションシップは、エンクロージャーとリピーターの内部にさらにエンクロージャーとリピーターが存在することを示唆します。そのことを考慮して、ページファイルの中心部分をモデル化したものは以下の通りです。
まず、HTMLテンプレートの設計の上部を説明しましょう。ここは、システム設計でのPickingUpの部分です。ここでは、大分類と小分類が一覧されているという見え方になっていますが、もう少し詳細に見ると、1つの大分類に対するその小分類が複数リストになっています。つまり、大分類と小分類が1対多の関係になっているものが、さらに、大分類の数だけ並んでいるということになり、全体から見れば、多数の大分類があり、さらに、その大分類に対応した多数の小分類があるという階層構造になっています。これらは、ネストしたエンクロージャー/リピーターで定義します。外側のエンクロージャー/リピーターは次のように定義するとします。class属性は、HTMLテンプレートでのクラス名をつけておきます。
<div data-im-control="enclosure" class="CategoryBox">
<div data-im-control="repeater" class="MajorCategoryBox">
<div data-im="****@category_name"></div>
</div>
</div>
このリピーターは、大分類の項目1つに対応します。なので、リピーターの中でcategory_nameフィールドを表示すると、このエンクロージャーに割り当てるコンテキストが大分類の一覧にしておくことで、ページ上には大分類だけの一覧が表示されます。コンテキスト名はまだ決めていないので、****にしておきます。
そして、このリピーターの中に、対応する小分類の一覧を表示するエンクロージャー/リピーターを定義します。次のようになります。
<div data-im-control="enclosure" class="CategoryBox">
<div data-im-control="repeater" class="MajorCategoryBox">
<div data-im="****@category_name"></div>
<div data-im-control="enclosure" class="MinorCategoryContainer">
<div data-im-control="repeater" class="MinorCategoryBox">
<button onclick="****">選択</button>
<div data-im="****@category_name"></div>
</div>
</div>
</div>
</div>
ここで、内側のエンクロージャー/リピーターの表示を行うためにリレーションの取得を行う場合、その時の外側のリピーターで展開している現在のレコードと照合する、つまりリレーションシップを考慮することが、定義ファイルでの設定で可能です。その仕組みを利用して、内側のエンクロージャー/リピーターでは特定の大分類に対する小分類だけが表示されるようになります。
引き続いてMemoLineのリピーターから続く2つのエンクロージャーについて説明します。ここではまず、memoテーブルだけだと、大分類や小分類は主キー値を記録していることで、どの分類なのかは記録されていますが、単なる数字なので、人間が目で見て判別するのは困難ということがあります。つまり、memoテーブルの値に加えて、選択した分類の名前を知りたい分けです。名前が別テーブルにあるので、つまりはそこでリレーションシップに応じたテーブル連結をするのがSQLでの定番ですが、UIレイヤーではそういう用途にselectタグによりポップアップメニューが使われてます。ポップアップメニューは、ここでは、top_category_id値を対応させることができますが、そのcategory_idに対応する値を内部で記録しつつ、ページ上には別の文字列を表示することもできます。この場合、categoryとmemoが1対多の関係になりますが、逆にメモの一覧にある大分類のポップアップメニューには、選択された大分類項目だけでなく、選択肢として表示されるように全ての大分類の項目が必要であり、それぞれoptionタグでselectタグの内部に記述しなければなりません。つまり、selectは選択肢の提供と、選択したものの名称表記の両方の機能を持っているのです。逆に言えば、selectを適切に使えば、選択肢の用意だけで選択したものの名前はそこにあるものを使うので、一石二鳥であるとも言えます。INTER-Mediatorでは、selectとoptionの関係をエンクロージャー/リピーターとして扱うので、その関係をモデル図の右下のあたりに2箇所記述しました。
INTER-Mediatorで一番難しいのは、このエンクロージャー/リピーターの特定、そして、その階層関係での定義ではないかと思われます。エンクロージャー/リピーターを特定するには、そこにリレーションが得られるという事実と関連づければ良いのですが、階層関係になると非常に難しくなるというのはあるでしょう。やはり、動作原理を知るのがしっかりとした設計を行うためには必要なことは言うまでもありません。
ここまでのところで5つのエンクロージャー/リピーターが出てきました。よって5つのコンテキスト定義が必要という見方もできます。ここで、コンテキストを実際に定義して行く作業に入ります。実際には4つの定義で問題ないことがわかります。
ここで、コンテキスト定義は、以下の図のように、Contextクラスを元にインスタンス化したオブジェクトとして記述することにします。現実には連想配列なのでコンテキスト定義は言語の意味でのクラスを元にはしていませんが、認識可能なキーが決まっているのは、属性が定義されていることと同義ですので、この点は問題ないと思われます。実際には、コンテキスト定義を利用して、コンテキストオブジェクトが生成されます。正確に記載すると以下の図の左側、Precise wayと書かれた方のクラス図になります。つまり、コンテキストオブジェクトConcreteContextObjectはテーブルConcreteTableを継承しているか、あるいは参照することになり、その結果、ConcreteEntityの配列がstore属性から参照できるようになっています。ここで、ConcreteContextObjectをインスタンス化した時、当然ながら継承元のフィールドがそのまま所有します。ここで、テンプレートバインディングやあれこれとUMLの仕組みを試したのですが、モデルとしてしっくりくるものがありません。そこで、右側のように、Concise wayと書かれたように、コンテキストで利用する実テーブルであるTableを継承して、ContextObjectを定義します。名前は任意にして、このような目的のクラスであることをステレオタイプのcontext objectで示すことにします。こうすれば、右下のようなオブジェクトが記述でき、テーブルのフィールドをそのまま持ちます。もちろん、この表現は不正確であり、コンテキストオブジェクトはTableによるレコードの配列を持つのではありますが、そこを正確に全部記述すると図が煩雑になるだけなので、「実際にはそういうことだけども勘弁性を重視してこのように書く」という合意ができたとして、Concise wayのような表記をします。
ここでHTMLテンプレートの設計を見ながら、コンテキスト定義のオブジェクトを検討します。ここでは代表的な属性だけを記述することにします。結果をまとめると以下の図になります。
まず、カテゴリの一覧部分、つまりHTMLテンプレートの設計でいえば、CategoryBoxクラスとMinorCategoryContainerクラスを取り出します。これらはエンクロージャーなので、リレーションと対応させないといけません。ここでは図で示せるように、tableステレオタイプのcategoryクラスを継承して、top_category_listとsub_category_listのコンテキストオブジェクトクラスを定義し、それらのインスタンスとして、緑色のボックスのオブジェクトを記述しましたが、これは実際にここまでの記述は不要でしょう。記述すべきは山吹色のボックスのコンテキスト定義のオブジェクトのみです。コンテキスト定義のtop_category_listでは、viewあるいはtable属性を見ることで、リレーションの元になっているテーブルが判別できるので、結果的に薄黄色と緑色のボックスは、コンテキスト定義の情報や、スキーマの情報からすぐに入手可能であるということです。
同様にして、memo_list、minor_category_list、major_category_listのコンテキストを定義します。memoテーブル由来のコンテキストはmemo_listのみですが、残りの4つのコンテキストはcategoryテーブルを由来としています。このように、同一テーブルからであっても場面によって(つまりコンテキストによって)必要とするデータは異なり、その結果定義は別々に必要ということになります。ここで、major_category_listとyop_category_listの定義は同一なので、これは一方だけを使うことでも構いません。もちろん、名前だけが違う同一の定義が2つあっても構いません。そこは好みの問題です。定義をよく見ると、parent_category_idがnullのレコードが大分類の項目であり、このリレーションはコンテキストには強く依存せず、どこでも同じ条件で取り出しができることになります。minor_category_listとsub_category_listはどうでしょうか? relationshipの定義が微妙に違います。カテゴリのリストの方は、大分類そのものと関連があるのに対して、メモの方はメモで選択されている大分類項目と関連があります。つまり、これら2つのコンテキストは、別々のものです。つまり、場面が違うものであり別々に定義しなければならないことになります。
ここまで設計すれば、あとは手を動かして実装するだけになります。必要な属性などを追加してページファイルを作り、コンテキスト定義をまとめて定義ファイルを作ります。
ここで、HTMLテンプレートのAllSelectButtonやMinorCategoryButtonを見てください。ここで、ボタンを押して、メモの一覧に検索をかけて絞り込んだり解除したりということが必要になっていますが、INTER-Mediatorの機能ではそれは含まれていません。そういう仕組みのボタンは提供されていないのですなので、ここはどうしてもJavaScriptで実装をしなければなりません。それぞれのボタンは次のように定義しましょう。
<button onclick="redrawMemoList()"
class="AllSelectButton">全選択</button>
:
<button onclick="redrawMemoList($)"
class="MinorCategoryButton"
data-im="sub_category_list@category_id@$onclick">選択</button>
<div data-im="sub_category_list@category_name"></div>
対応するプログラムは例えば次のように作成します。つまり、memo_listコンテキストに対する検索条件を、状況に応じて切り替えれば良いということです。
function redrawMemoList(id) {
let idValue = parseInt(id)
INTERMediator.clearCondition('memo_list')
if(idValue) {
INTERMediator.addCondition('memo_list',
{field:'sub_category_id',operator:'=', value:idValue)
}
const context = IMLibContextPool.contextFromName('memo_list')
INTERMediator.constructMain(context)
}
以上がINTER-Mediatorでの実装設計になりますが、実質的には、ページファイルや定義ファイルがモデルそのものであるとも言えます。一度実装に近いクラス図などを記述するのも良いのですが、結果的にHTMLや定義ファイルを書いてしまった方が早いとも言えます。つまり、デザイン時の作成物と、目的とする作成物が近いというのがこうしたツール(いわゆるローコードツール)の特徴なのではないかと思われます。
そして、INTER-Mediatorでは、エンクロージャー/リピーターの理解は不可欠です。このようなツールごとに理解しておかないといけない特有の概念は必ずあり、それが最も注目すべき制約ということになるのではないかと思われます。