不可能は可能です。 ステートレスアプリでのステートフルな動作!

Webアプリケーションを開発する場合、何らかのアクションを実行するプロセスで、ユーザーとの対話的な通信が必要になることがよくあります。 Web ERPシステムは、そのような通信に非常に具体的な要件を課しています。 そのようなシステムのいくつかのオプションを運用した後、私が最も受け入れられると思われる方法を見つけました。 次に、サーバーでアクションを実行するときに、ユーザーとの対話型作業の問題に対するソリューションを共有したいと思います。













そのため、複雑なWebアプリケーションを開発する場合、競合するいくつかの要件を組み合わせる必要があります。

  1. ビジネスソリューションアプリケーションコードには、ビジネスタスクに関連しない最小限のアーティファクトを含める必要があります
  2. ビジネストランザクションは、1つの不可分なトランザクションで発生する必要があります
  3. コードの実行中に、ユーザーとのインタラクティブな通信が必要です
  4. ユーザーからの応答を待つことは、サーバーリソースを占有して他のユーザーの作業をブロックしてはなりません。
  5. アプリケーションコードはサーバー上で実行され、クライアントとの通信はブラウザを介して行われます




一般的なビジネスタスク:注文;注文プロセス中、サーバーでチェックが行われ、オペレーターからのインタラクティブな入力が必要です。 たとえば、特定の条件が満たされたときに、ユーザーにアクションの確認を求めます。



ビジネスコードの単純さの要件に従って、アプリケーションコードは次のようになります(注意、擬似コード)。



var  = ();  (.()) { (“      , ?”); } ();
      
      







同時に、確認の行では、魔法を発生させたいと考えています。

  1. コードが一時停止しました。
  2. 承認されていない顧客フォームは、はい/いいえのリクエストを受け取りました。
  3. 彼が「はい」をクリックした場合-作業を続けます。
  4. 「いいえ」の場合-フォームデータを再入力して、もう一度送信します。




あなたの「魔法の」ソリューションを提供します。これはすでに試されており、私のプロジェクトでうまく機能しています。



典型的な「はい/いいえ」ダイアログを発行する問題を解決してみましょう。



主な問題は、いくつかの理由でユーザーからの応答を待っている間、サーバーで操作を中断できないことです。

  1. すべてのクライアント/サーバー通信は非同期で、ステートレスプロトコルに従って行われます。
  2. ユーザーからの応答を待っている間はリソースを取得できません


ただし、ユーザーはアクションの継続性の印象を持っている必要があります。 また、プログラマーは、自分のコードの実行が中断される可能性があるという事実を考えるべきではありません。 したがって、いくつかのトリックを使用する必要があります。



最初に、リクエスト識別子とアクションの概念を紹介します。

リクエストID-クライアントで生成される一意のリクエスト識別子。 サーバーへの各要求には、ユーザー入力を要求する場合を除き、独自の識別子があります。

アクションID-一意のアクション識別子。 より具体的には、ユーザー応答が要求されるコード内の場所の一意の識別子。



これらの2つの識別子を使用すると、ユーザーからいつ何が要求され、どのような答えが返されたかを正確に判断できるように、クライアントサーバー操作スキームを整理できます。



この回路は次のようになります。





したがって、開発者は、自分のメソッドが1回だけ実行されるという印象を持っています。 ユーザーもそれぞれ1つのアクションのみを実行したようです。



このアイデアがプログラマーにどのように見えるかを考えてみましょう。

 public class HelloNewWorldOrder { // ,   Guid guid = new Guid("5FFD6DB4-1201-44BF-9DE0-DC199AC004D9"); public void KillAllHumans(Human[] humans) { foreach (var human in humans) { if (human.Name == Context.Current.User.Name) { //      ExceptionHelper.Interactive(guid, "     .   ?"); } human.Kill(); } } }
      
      







私の意見では、それはかなりフレンドリーに見えます:)



ExceptionHelper.Interactive自体は次のようになります。

 public static void Interactive(Guid id, string message) { //    var key = RequestHelper.GetRequestId(); var exists = Query.All<InteractiveException>() .Any(r => r.RequestId == key && r.ExceptionId == id); if (exists) { return; } throw new InteractiveException(message, id, key); }
      
      







データベースにスキップされた例外のレコードのみを追加することは残ります。 たとえば、これは、ベースコントローラーであるGlobal.asaxで、またはユーザーとのこのような通信が予想される場所で実行できます。



このように簡単な方法で、ユーザーアクション間の状態を保存するデスクトップアプリケーションの動作がエミュレートされるようになりました。 この場合、アプリケーションの実際の状態はどこにも保存されず、リソースはブロックされず、ユーザーの応答時間に制限はありません。



http://demo.oreodor.com/Parts/Main.aspx#Order:Regularで実際にこのシステムを試すことができます 。 この記事で説明されているアプローチは、チェックアウト時にそこで使用されます。



検証アクションのソースコードは次のようになります。



 /// <summary> ///   /// </summary> [Icon(ExtIcon.Accept)] public class OrderCompleteAction : IAction<IFormContext<Order>> { /// <summary> ///  1 /// </summary> private Guid e1 = new Guid("84099696-2225-41F9-AF54-0BE66367CEAA"); /// <summary> ///  1 /// </summary> private Guid e2 = new Guid("26142EDB-3DC8-4B00-920F-FA33FC3ADF40"); /// <summary> ///   /// </summary> /// <param name="context"> </param> public void Execute(IFormContext<Order> context) { Assert.That(context.Item, Is.Not.Null, "   , !"); var cpus = context.Item.Items.Select(m => m.Linked) .OfType<Cpu>().Select(c => c.SocketType.SysName).ToHashSet(); var mbs = context.Item.Items.Select(m => m.Linked) .OfType<MotherBoard>().Select(c => c.SocketType.SysName).ToHashSet(); var coolers = context.Item.Items.Select(m => m.Linked) .OfType<Cooler>().SelectMany(c => c.Sockets.Select(m => m.Linked.SysName)).ToHashSet(); if (!cpus.IsSubsetOf(coolers)) { ExceptionHelper.Interactive(e1, "       ."); } if (!cpus.IsSubsetOf(mbs)) { ExceptionHelper.Interactive(e2, "       . ."); } context.Item.Status = Status.BuiltInStatuses.Work.ToEntity<Status>(); context.ShowMsgBox("   ."); } }
      
      






All Articles