注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。
Slate では、すべてのウィジェット、関数、変数、クエリがグラフ内のノードとしてモデル化されます。各ノードは評価されて JSON を出力し、ほかのノードはその出力を参照するようにテンプレート化されます。これらの参照によってノード間の依存関係が定義され、Slate はこの依存関係を使って、何かが変化したとき(新しいユーザー入力、クエリの実行、変数の値の更新など)に、どのタイミングでノードを再評価するかを判断します。
Slate アプリケーションを「開発」する際の主要な作業は、出力の表示と入力の取得に使うウィジェット、表示用のデータの簡単な加工とアプリケーションのログインの処理に使う関数、データのクエリやほかの Foundry サービス API への接続に使うクエリを追加してこれらのノードを定義し、Handlebar 構文でその依存関係を設定することです。
この依存関係グラフは、依存関係タブでいつでも表示でき、Handlebar の参照を通じて設定した関係を Slate がどのように解釈しているかを理解できます。このグラフ表現_こそが_アプリケーションです。グラフが「間違っている」と思えるものを表している場合は、グラフがアプリケーションを誤って表現していると決めつけるのではなく、_なぜ_そうなっているのかを理解することから始めます。
アプリケーションで予期しないことが起きたときは、まず依存関係タブで、想定どおりに動作していないウィジェットの上流ノードを確認します。無関係なクエリやユーザー入力に基づいてノードが再評価される原因となりうる、予期しない関係を探します。
依存関係グラフは「遅延評価」を行うものと考えると役立つ場合があります。上流の参照の値が変わったときにのみノードを再評価することで、不要な処理を避けます。そのため、上流ノードが以前と同じ値に更新された場合には、混乱を招く可能性のある動作が起こります。下流のクエリは実行されず、下流のウィジェットは再描画されません。これは、ユーザー入力をリセットするパターンでよく見られます。Foundry 内で依存関係グラフとイベントフレームワークの入門チュートリアルとウィジェットの選択をリセットする例を検索すると、この動作と、Math.random() を使って依存関係グラフの評価にエントロピーを加え、ノードが確実に再評価されるようにする回避方法を確認できます。
Slate は依存関係グラフを使用して、上流の依存関係の状態に基づいて関数を再計算するタイミングやクエリを再実行するタイミングを判断する複雑な処理を管理します。ほとんどのワークフローはこの機能を使って設定できるため、Slate 開発者は、可能な限り依存関係グラフを利用することを常に最初に検討すべきです。
ただし、依存関係グラフを明示的なトリガーとアクションのペアで補完することが適切な場合もあります。Slate では、これを「イベント」と呼びます。単純な例としては、クエリの完了時にバナーやトーストを表示する、ユーザーが詳細を表示するために何かをクリックしたときにダイアログを開く、複数の入力を設定した後にボタンのクリックでクエリを実行する、といったものがあります。
イベントフレームワークの強力さを理解すると、Slate でのあらゆる開発タスクをイベントベースの解決策で扱いたくなるかもしれません。その結果、1つのアプリに数百ものイベントとアクションのペアが含まれるという、よくある失敗パターンに陥ることがあります。技術的には可能ですが、この複雑さによってアプリケーションの期待される動作を順に確認することが難しくなり、簡単なデバッグさえできなくなる場合があります。
目安として、イベントの「チェーン」はできるだけ短く保ち、イベントよりも依存関係グラフに依存する解決策を優先します。アプリのイベント数が50を超えるようになった場合、特にそれらのイベントがトーストの表示やクエリの開始以上のことを行っている場合は、一度立ち止まってアプリケーションのアーキテクチャを慎重に見直すよいタイミングです。リファクタリングによって開発を簡素化したり、アプリケーションをさらにサブアプリケーションに分割したりできる箇所がないか検討します。
イベントフレームワークと、一般的な基本イベントパターンの例について、詳しくはこちら。
組み込みのイベントタイプに加えて、html およびテーブルタイプのウィジェットでカスタムイベントを作成し、これらのウィジェット内の異なる要素がユーザーのクリックによって異なるイベントを発信できるようにすることもできます。
これらのカスタムトリガーの設定は2つのステップで行います。
slClickEvent プロパティを使って HTML 要素にトリガーを定義します。Copied!1<div class='pt-button' slClickEvent='delete'>Delete Me</div>
新しいイベントを登録すると、イベントトリガーを選択できる場所に、myWidgetName.click.delete のような新しい選択肢が表示されます。クリックイベントを定義する HTML を動的に生成する場合でも、それらのイベントの登録はハードコードする必要があります。これにより、条件によってはその時点でボタンが実際には生成されていなくても、Slate は使用される可能性のあるトリガーを把握できます。
これらのイベントを使って、HTML でカスタムボタングループを作成できます。また、以下のイベント値を渡すで説明するイベント値と組み合わせて、項目ごとに独自のアクションを持つリストやテーブルを作成することもできます。
追加の条件(単にトリガーが発火すること以外の条件)を適用する必要がある場合は、イベント設定の JavaScript 部分を使って追加のロジックを実行できます。ロジックを評価し、イベントがアクションを実行すべきでない場合は、特別な値 {{slDisableAction}} を返します。この値はイベントを恒久的に無効にするものではなく、そのイベントアクションの特定の実行のみを中断します。
ベストプラクティスとして、イベントの JavaScript 内で複雑なロジックを開発することは避けてください。Linter での解析やエラー報告がないため、誤りや未対応のエッジケースによってコードの実行に失敗しても通知されず、予期しない動作につながる可能性があります。代わりに、イベントの有効性を判定するロジックをカプセル化した関数の実装を検討します。そうすれば、イベントの JavaScript は次のように簡潔にできます。
unless ({{f_checkValidAction}})
return {{slDisableAction}}
ただし、イベントに debugger() ステートメントを記述し、Chrome Developer Tools を使って実行時に停止してロジックをステップ実行することはできます。
slClickEvent プロパティに加えて、特定の HTML 要素に slClickEventValue を定義できます。このイベントがトリガーされるたびに、特別な変数 {{slEventValue}} を使って、イベントの JavaScript から関連する値を参照できます。
これにより、動的なインタラクションのさまざまなパターンを実現できます。
slClickEvent と slClickEventValue を定義する関数を作成します。variable.changed イベントトリガーから後続のアクション(最も一般的なのはクエリ)をトリガーし、変数の値を参照します。たとえば、このパターンを使って、テーブルの行の横に小さな 'x' を追加できます。この要素では、slClickEvent を 'delete'、slClickEventValue を行の主キーに設定します。その後、myTable.click.delete イベントの発火時に、v_idToDelete という変数の値をイベント値に設定できます。次に、v_idToDelete.changed イベントに基づいて q_deleteRow クエリを実行し、この短いチェーンを完成させます。
これよりはるかに長い、または複雑なチェーンを作成しないよう注意してください。以下の循環依存関係に関する警告も参照してください。大規模な構成、ネストした構成、その他の複雑に入り組んだ構成ではなく、短く明確に分かれたイベントを優先します。複雑な構成は、動作の理解やデバッグが難しくなり、予期しない動作につながることがよくあります。
Slate の依存関係グラフは、技術的には「有向非巡回グラフ」(DAG)です。これは、ノード間の関係に方向があり(ルートノードからリーフノードへの階層があり、ノードの解決はその方向に進みます)、グラフ全体に循環があってはならないことを意味します。つまり Slate は、依存関係グラフ内に、長さにかかわらず「ループ」が設定されることを防ごうとします。
ただし、イベントを使って変数の値を設定したりクエリをトリガーしたりすると、アプリケーションにループを追加できてしまい、非決定論的な動作につながります。循環依存関係が必要に思えるワークフローがある場合は、一歩引いて代替パターンを検討してください。ほとんどの場合、目的のワークフローの機能を実現する方法はあります。
依存関係グラフの仕組みでは、デフォルトでページの読み込み時に依存関係グラフ全体が解決されます。つまり、グラフ内の各ノードを解決するために、すべてのクエリと関数が必要な順序で実行されます。実際には、開発中に注意を払っていないと、ページの読み込み性能が徐々に低下し、やがて断続的にタイムアウトが発生することがよくあります。ほとんどの場合、根本的な原因は、上流のクエリへの依存関係を持たない「ルート」ノードのクエリが多数あることです。つまり、これらのクエリがすべて同時に実行を試みます。その結果、ブラウザーの同時接続、同じテーブルへの同時クエリ、アプリケーションに戻されるネットワークトラフィックが多すぎるために、キューイングや負荷制限が発生することがあります。最終的には、クエリの失敗が非決定論的に発生し、ページの読み込み時に、アプリケーションが完全に動作しなくなるまではいかなくても、動作が遅く感じられる状態になります。
Postgate では、アプリケーションのユーザーごとに専用の接続プールが作成されます(最近そのアプリケーションをすでに使用していた場合は、キャッシュから復元されます)。各接続プールの同時接続数は10に制限されています。Postgate は、そのアプリケーションのすべてのユーザー、および同期されたデータを使用するほかのアプリケーションで共有されるため、この制限によって、1つのアプリケーションインスタンスが Postgate に過負荷をかけることを防ぎます。性能面では、この制限により、すべてのクエリを一度に通す場合(クエリが10個を超える場合)よりも、実際には性能が向上します。Postgres は、同時に応答するクエリが少ないほど、全体として高速になるためです。
10個の接続がすべて使用されると、残りのクエリリクエストはキューに入り、接続が利用可能になり次第、それぞれが接続を取得します。ただし、接続を無期限に待たせるのではなく、接続プールは、接続を5秒間待機したクエリをタイムアウトさせます。つまり、実行に数秒かかるクエリがいくつも接続プールを占有していると、本来は高速なクエリ(たとえば、テストボタンを使用して単独で実行した場合に高速なクエリ)でも、長い時間がかかったり、タイムアウトしたりすることがあります。
この問題の解決策には、いくつかの要素があります。
2番目の点を詳しく説明すると、すべての開発において、無駄のないコードを目指すべきです。コードは、必要な処理だけを、できる限り効率的な方法で行う必要があります。Slate のクエリでは、同じ処理を何度も繰り返すことに多くの時間を費やしてしまい、これがよく問題になります。ユーザーがカテゴリーを選択するためのドロップダウンに選択肢を設定する、次の単純なクエリを考えてみます。
Copied!1SELECT DISTINCT(COALESCE(category_col)) FROM "my_table"
この場合、ページを読み込むたびに、Postgres はすべての行の category_col の値を調べて null かどうかを確認し、重複のないすべての値のリストを生成する処理を繰り返す必要があります。トランスフォームと新しい派生データセットを使用すれば、この処理は、上流で新しいデータが追加されるたびに1回だけ実行すればよくなり、クエリは次のように単純になります。
Copied!1SELECT * FROM "my_table_categories"
たとえば、ユーザーの入力に対応する必要がある場合など、SQL の式を完全にはなくせない場合でも、できる限り多くの処理を上流のトランスフォームに移して、より単純なクエリを作成することを目指すべきです。「処理を行う」回数をできる限り少なくするという原則を常に意識することで、より安定した高性能なアプリケーションに近づきます。
クエリを実行するタイミングを制御する2つの一般的なパターンについて、以下で詳しく説明します。これらをアプリケーションで使用すると、ページの読み込み時に実行されるクエリの数を大幅に減らし、アプリケーションが期待されるとおりに動作するようにできます。
条件付きクエリでは、クエリの実行を最も細かく制御できます。また、「イベントでトリガー」または「手動」の設定(後述)と組み合わせて、クエリの実行に必要な論理条件を追加できます。クエリの実行条件を有効にするには、実行の横にあるドロップダウンの下の、さらに...のチェックボックスをオンにします。
最も単純な場合、すべての依存関係が null ではないという選択肢を使用すると、クエリまたはそのパーシャル内のハンドルバー式のいずれかに値が指定されていない場合に、クエリが実行されなくなります。これは、有意義な結果を得るためにユーザーの入力が必要なクエリで特に有用です。WHERE 文が不完全なために失敗するクエリについて、データベースとの不要な往復を防ぎます。
クエリを実行するタイミングを制御する簡単な方法は、実行ボタンの横にあるドロップダウンのチェックボックスを使用して、クエリを手動に設定することです。
手動実行に設定したクエリは、イベントまたは手動で指定した依存関係によってトリガーされた場合にのみ実行されます。
これは、ユーザーの操作によってトリガーされるべきクエリに適したパターンです。ただし、あまりにも多くのクエリを連鎖させると、不要な複雑さにつながることがあります。上記のイベントセクションのガイダンスを参照してください。手動クエリがトリガーによって実行されると、依存関係グラフは下流の依存関係を自動的に更新します。q_queryName.success に基づくイベントを追加する必要はありません。
関数を使用すると、ユーザーの選択やアプリケーションの状態に基づいて、データに簡単な加工を施したり、ロジックを実行したりできます。見落とされがちなパターンや機能をいくつか活用すると、関数をより整理された形でシンプルに扱えます。
ユーザー入力を検証するの例を見ると、検証関数が単一の値ではなく、JavaScript オブジェクトを返していることがわかります。Slate はこれらのオブジェクトをパースし、アプリケーション内のほかのどこからでも Handlebars を通じて利用できるようにします。
つまり、たとえば異なるウィジェットで使用する新しい表示用の列をいくつか導出する必要がある場合、列ごとに関数を作成する代わりに、必要な表示値をさまざまな形式で生成し、それらをすべてオブジェクトにまとめて返す単一の関数を作成できます。次のセクションでは、この例を詳しく見ていきます。
ロジックを少数の関数にまとめる方法と、分割する方法の間には、常にバランスがあります。まとめることで、重複作業を減らし、類似した処理をまとめて管理できます。一方、分割することで、個別の操作をカプセル化し、必要なコードだけを見つけやすくできます。このトレードオフを意識し、アプリケーションを改良する過程で関数を定期的にリファクタリングします。
チャートに表示するためにデータを整えたり、テーブルに表示する HTML を生成したりするために、少し追加の処理が必要になることがよくあります。たとえば日付の書式設定など、可能な場合は、データの変換と準備の段階でこの処理を行うことを検討してください。これは、処理の実行回数をできるだけ少なくするという原則に従うものです(つまり、ユーザーごとにページを読み込むたびに処理するのではなく、トランスフォームで1回だけ処理します)。
それができない場合は、Slate が想定するデータ構造を保持しながら、列の値を1つずつ変換して新しい値を生成するシンプルな関数を記述できます。
Copied!1 2 3 4 5 6 7 8`// f_deriveDisplayColumns var data = {{q_myQuery.results.[0]}} data.newColA = _.map(data.primaryKey, (value,key,index) => { // 新しい列を導出する処理 return newColValue }); return data;`
このスニペットでは、Lodash ↗ 版の _.map() 関数を使用しています。この場合は、組み込みの Array.map() も同様に使用できます。Lodash のヘルパー関数は JavaScript オブジェクトの操作に非常に役立ち、ゼロから実装し直すとバグが発生しやすい複雑なアルゴリズムも多く実装されているため、これらの関数に慣れておくと役立ちます。
関数が完成したら、アプリ内のどこでも、{{q_myQuery}} を直接参照する代わりに {{f_deriveDisplayColumns}} を参照できます。この関数は、元のクエリのレスポンスオブジェクトと同じ構造を持ち、そこに「列」が追加されているだけだからです。
かなり複雑な JavaScript コードを実装する必要がある場合は、関数エディターの左下にあるカスタムライブラリに移すことを検討してください。ライブラリを使用すると、どの Slate の「関数」でも純粋な JavaScript 関数を利用できるため、同じヘルパー関数を何度も実装せずに済みます。
ライブラリは純粋な JavaScript であるため、その内部では Handlebars を使用できません。その代わり、通常の JavaScript と同様に、Slate の「関数」からライブラリ関数を呼び出すときにパラメーターを渡せます。これらのパラメーターには、Handlebars の値や、ほかの方法で動的に生成した値を指定できます。
ライブラリのコードでも関数でも、常に時間をかけてコードを整理し、直感的には理解しにくい可能性のある箇所を文書化してください。「自己文書化された」コードを目指すことはできますが、ロジックをわかりやすく簡潔に文書化する習慣をつけておくことは、将来の開発者や保守担当者への配慮になります。