web-dev-qa-db-ja.com

UIの応答性と競合状態の回避のバランス

スコープを小さく保つために、同じユーザーが同じアプリセッションで開始したUIの競合状態について説明します。質問は一般的であり、モバイル、Web、またはデスクトップUIに固有ではありません。

問題

最新のUIは応答性が高いと予想されます。つまり、UIが応答性があり、利用可能なままであるときに、ユーザーが非同期に実行したコマンドが実行されます。これは、あらゆる種類の競合状態にとって最適な繁殖地です。非同期コマンドは潜在的に多くの時間を要する可能性があり(たとえば、接続が遅い/応答しない場合)、その間、ユーザーは競合する可能性のあるコマンドを並行して開始できるためです。

すべてのコマンドを同期化したり、画面を無効にしたり、コマンドのブロック進行状況バーやリングを表示したり、モーダルダイアログをブロックしたりするなど、「ブルートフォース」の回避策がありますが、ここでは考慮しません。

私の経験では、競合状態やコマンドの競合につながる可能性のあるすべてのUI要素を追跡し、それに応じてそれらを有効/無効にすることを覚えておくことは、深刻な精神的オーバーヘッドです。見返り(より良いUX)は常に価値があるわけではありません。 Rx/Reactiveプログラミングを使用すると、UI要素を自動的に有効/無効にすることが簡単になりますが、多くの依存関係を頭に置いておく必要がありません。多くの人がプロジェクトに取り組み、別々のコンポーネント/ビューを別々に書くと、それは特に悪いことです。

たとえば、注文を編集するためのシンプルなアプリを考えてみましょう。注文にラインがあります。いくつかの非同期コマンドがあります(たとえば、それらはすべてデータベースまたは他のバックエンドに熱心に書き込みます)。

注文コマンド(UI要素あり)

  • 削除(ボタン)
  • 顧客を変更(ドロップダウン)
  • 出荷日を変更する(カレンダー)
  • 確認(ボタン)

行コマンド

  • 行を作成(グリッドの新しい行)
  • 行を編集(グリッドの行)
  • 行を削除(ボタン)

安全に実行できる、または同時に実行しても安全ではないコマンドの例を考え出すのは簡単です。例えば。これらのコマンドが干渉しないため、行を編集して別の行を同時に削除しても問題ありません。ラインの保存中に注文の出荷日を変更することはできますが(出荷日はラインに影響しません)、注文の顧客を変更することはできません(たとえば、ラインの価格設定が更新されるため、進行中のラインコマンドはありません)。行なしの注文のみ削除できるというルールを追加するとします。進行中の行コマンド(たとえば、行の追加)がある場合は、[注文の削除]ボタンを無効にする必要があります...など。

大規模なアプリでは、エンティティとそれらの間の依存関係が多くなるため、微妙な競合状態のリスクがあります。

例は説明のためだけであり、質問はより一般的です。

質問

UX /応答性と競合するコマンド/競合状態を許可するリスクとのバランスを保つための規律あるアプローチを知っていますか?私は特効薬を期待していませんし、答えが短くなく、他のリソースや本への関連リンクを答えとして受け入れると仮定します。任意のプラットフォームまたは言語で機能しますが、理想的には、Rx /(関数)リアクティブプログラミングの精神に基づいている必要があります。

[〜#〜] update [〜#〜]議論に基づいて、単純な販売注文アプリはやる気を起こさせる例ではないので、人々はどのような種類のアプリケーションがそれを気にする必要があるのか​​疑問に思います。私は同意します:より良い例はVisual Studioまたはお気に入りのIDEです:ビルドの実行中/ソースがgitからフェッチされている間など、一部のコマンドは無効になります。IDE =すべてのバックグラウンドコマンドで、それらのコマンドはしばらく続く可能性があります。IDEのようなプロジェクトは、コマンドの競合を回避するために非常に体系的なアプローチを使用している可能性が高いです販売注文のようなアプリのメリット)

5
KolA

極端な場合、コマンドパターンと組み合わせて、プロデューサー/コンシューマーパターンを使用できます。基本的に、ユーザーアクションをキューに入れ、それらを順番に実行します。これはスレッドの競合を解決します。これを拡張して、一部の操作を他の操作と並行して実行することができます。つまり、読み取りは互いに並行して実行できますが、書き込みと並行して実行することはできません。

データベースの場合、これはその上に新しいレイヤーを追加することを意味するため、アプリはそのレイヤーとのみ対話し、DBまたはORMとは直接対話しません。

1
Ccm

私の手作りのアプローチについて説明します。100%満足しているわけではありませんが、多くのケースをカバーしており、全部かゼロかを選択できます。 (プロジェクトはXAMLにあり、私は Nito Calcualted Properties およびRxをMVVMに使用しましたが、他の場所で繰り返すことができます)

ネストされたコマンドスコープの動的ツリーがあり、すべてのコマンドに関連付けられたスコープまたはいくつかのスコープがあります。

ルールは簡単です

  • コマンドは、そのスコープが解放されるまで実行できません
  • コマンドは実行中にスコープを排他的にロックします
  • コマンドが終了(または失敗)するとすぐにスコープが解放されます

コマンドをトリガーするUIコントロールは、反応的に無効または有効になります。スコープのロック/ロック解除は、メインUIスレッドで同期的に行われます。

スコープの例

  • App(グローバルスコープ)、アプリが完全にアイドルになるまでこのスコープでコマンドを実行できません。アプリスコープがロックされている間は他のコマンドを実行できません
  • App/Orders-すべての注文に関連するコマンド(想像できません)
  • App/Orders/{OrderId}-1つの注文に関連するコマンド。このスコープのコマンドは、注文のヘッダーと行IDをロックします。
  • App/Orders/{OrderId}/Lines-すべての行に関連するコマンド(考えられない)
  • App/Orders/{OrderId}/Lines/{LineId}-ラインに固有のコマンド
  • App/Orders/{OrderId}/Header-注文ヘッダー情報に関連するコマンド。アドレスなど(行スコープと交差しない/同時に編集可能)
  • App/Products/...など-他のコマンドのスコープ

メリット

  • ローカル推論:コマンドを追加または変更するときは、独自のスコープのみを考慮する必要があります
  • シンプル:階層内のスコープの数はコマンドの数よりも少なく、頻繁に変更されません
  • 通常、コマンドに関連付けられたスコープを特定するのは簡単です
  • ある程度の柔軟性:疑わしい場合は、より粗いスコープを選択するか、必要に応じて後で調整する
  • 再現性/ドメインに依存しない。スコープ階層の編集は簡単です(私の場合はタイプセーフです)

「コマンドスコープマネージャー」は、ビューがアンロードされた後でもロックされたスコープを追跡するために、ビューモデルから切り離されたシングルトンにすることができます。これにより、ユーザーは画面(ロード/アンロードビュー)間を行き来しながら、コントロールを無効または有効にできます。

1
KolA