GISユーティリティ:非同期相互作用モデル

GISユーティリティとのやり取りの経験を共有し続けています。 安全な接続を確立した後の次のタスクは、メッセージングの編成でした。 GIS住宅および共同サービスの開発者は、同期と非同期の2つの相互作用モデルを提供します。 一部の開発者は、そのシンプルさとアクセシビリティのために同期モデルを選択します。 この記事では、非同期モデルを使用する必要がある理由を説明し、C#での実装のヒントを示します。



相互作用モデルの選択



同期モデルは、古典的な「要求-応答」を意味します。 リクエストオブジェクトを作成し、それをGISハウジングおよび共同サービスに送信すると、システムは、応答が生成されるか、タイムアウトによって接続が切断されない限り、接続を保持します。



非同期モデルは、アクションを実行するようにタスクを設定することと、アクションのステータスを要求すること(必要に応じて数回繰り返します)の2つの操作で構築されます。 この相互作用のモデルは、GISハウジングおよび共同サービスの開発者によって推奨および推奨されているものとして呼び出されました。 次に、これら2つの操作を詳細に検討します。



アクションを実行するためのタスクの設定



情報を受信したり、データをGISハウジングおよび共同サービスに送信するには、リクエストオブジェクトに入力する必要があります。 「情報を取得する必要がある家」または「GIS住宅および共同サービスで作成する必要がある個人アカウント」の情報が含まれています。



メッセージは、メッセージ識別子(MessageGUID)も識別し、情報システム内のメッセージを一意に識別します。 同じMessageGUIDを使用したリクエストは、GISハウジングと共同サービスに複数回送信でき、GISハウジングと共同サービスにより、確実に1回実行されます。 たとえば、個人アカウントを作成するためのタスクを設定するとき、リクエストがタイムアウトによって落ちたり、接続が突然中断した場合、追加の個人アカウントを作成しないという自信を持って同じリクエストを再度送信できます。



オブジェクトの配列を送信する場合、TransportGUIDは各オブジェクトに割り当てられ、リクエストとレスポンスのオブジェクトを一致させることができます。 たとえば、処理の結果から、メッセージ全体からのこの特定の個人アカウントがGISユーティリティで受け入れられない理由を見つけることができます。



応答として、別のMessageGUIDを取得します。これは、GISユーティリティのリクエストIDです。 この識別子によって、GIS住宅および共同サービスでのリクエストのステータスがリクエストされます。



アクションのステータスをリクエストする



実行ステータスを取得するには、短いgetStateResultリクエストを実行する必要があります。 GISユーティリティで割り当てられたMessageGUIDを示します。 このリクエストは複数回送信できます。



応答では、メッセージ処理ステータスを受け取ります:承認済み、処理中、準備完了。 メッセージが処理されると、GISハウジングおよび共同サービスは、処理の結果(受信したオブジェクトまたはデータ送信に関する情報)を返すか、メッセージの処理中に発生したエラーを報告します。 最も一般的なエラーは次のとおりです 。“ EXP001000:内部エラー。 "、" データプロバイダー組織 "*"機関 "* "のアクセスが拒否されました ' '、 " リモートサーバーが予期しない応答を返しました:(502)Bad Gateway。 「、」 api.dom.gosuslugi.ru *のリッスンは、メッセージを受信できるエンドポイントによって実行されませんでした。 「など。 実際、メッセージは2つのタイプに分けられます。再送信できるメッセージと、再送する意味がなくなったメッセージです。 処理済みのGISハウジングおよび共同サービスエラーを受け取った場合、リクエストを再度送信しないことを決定しました。 リクエストがタイムアウトしたか、コードに問題がある場合は、メッセージを再度送信します。



GISの住宅および共同サービスの典型的な間違い



  1. GISハウジングおよび共同サービスの側でのメッセージ処理の実行は、トランザクションでは行われません。

    たとえば、30個の個人アカウントを作成するメッセージを送信すると、結果として「 EXP001000:Internal error。 「。 個人アカウントが1つも作成されていないと予想していますが、検証チェック中にすべての個人アカウントが作成されたことがわかります。



  2. 一部の要求は、無期限に「承認済み」または「処理中」のステータスで「フリーズ」します。

    通常、メッセージの処理結果は数秒で受信できますが、一部のメッセージは未処理の状態で数日間ハングアップします。そのようなメッセージは、処理状態を再度尋ねないように、その側でマークする必要があります


このような「機能」により、情報をGISの住宅および共同サービスに送信するプロセスは3つの段階に分けられます。



  1. GISユーティリティから情報をダウンロードして現在のステータスを確認する
  2. GISの住宅および共同サービスに必要な情報を送信する
  3. ダウンロードした情報を確認すると、「 EXP001000:Internal error。 」、そして情報が作成されました。


相互作用の技術面



GISハウジングおよび共同サービスとのやり取りのプロセスは、3つの段階で構成されています。



  1. メッセージの情報を取得し、データベースに保存します
  2. GIS住宅および共同サービスのプロキシオブジェクトの作成(WCFを介して作業していることを思い出します)、メッセージの送信、応答の処理、MessageGUID GIS住宅および共同サービスの保存
  3. 処理結果の取得、結果の処理


メッセージ作成



相互作用のタイプごとに、データベースにExportHouseInfoMessagesやImportLsMessagesなどのテーブルを作成し、GISユーティリティに送信するプロキシメッセージオブジェクトを作成するために必要なすべての情報を保存します。 この時点で、MessageGuidメッセージを作成します。



この段階では、メッセージがまだ作成されておらず、処理の結果が受信されていないデータからのみメッセージを作成する必要があります。そうでない場合は、データを複製できます。



この段階の主な問題は、情報システムから送信に必要なデータを取得することです。 たとえば、GISの住宅および公共サービスでは、個人アカウントのすべての変更を送信する必要があり、適切なコンテキストでの変更の履歴はありませんでした。



C#実装例
/// <summary> ///       -   /// </summary> /// <typeparam name="TMessageDomain">  </typeparam> /// <typeparam name="TSourceDomain"> ,     </typeparam> public class CreateMessageCoreService<TMessageDomain, TSourceDomain> where TMessageDomain : MessageDomain { private readonly ISourceService<TSourceDomain> _sourceService; private readonly IMessageDomainConverter<TMessageDomain, TSourceDomain> _messageDomainConverter; private readonly IMessageDomainService<TMessageDomain> _messageDomainService; private readonly IOrgPPAGUIDService _orgPPAGUIDService; private readonly IGisLogger _logger; public CreateMessageCoreService(ISourceService<TSourceDomain> sourceService, IMessageDomainConverter<TMessageDomain, TSourceDomain> messageDomainConverter, IMessageDomainService<TMessageDomain> messageDomainService, IOrgPPAGUIDService orgPPAGUIDService, IGisLogger logger) { _sourceService = sourceService; _messageDomainConverter = messageDomainConverter; _messageDomainService = messageDomainService; _orgPPAGUIDService = orgPPAGUIDService; _logger = logger; } public void CreateMessages(CoreInitData coreInitData) { var stopWatch = new Stopwatch(); stopWatch.Start(); try { //    ,      var sourceDomains = _sourceService.GetSourceDomains(coreInitData); // senderId   var orgPPAGUID = _orgPPAGUIDService.GetOrgPPAGUID(coreInitData.UkId); //      var messages = _messageDomainConverter.ToMessageDomain(sourceDomains, coreInitData, orgPPAGUID); //     _messageDomainService.InsertMessageDomains(messages); stopWatch.Stop(); _logger.Info(this.GetType(), $" {messages.Count}     {coreInitData.UkId}  {stopWatch.Elapsed}"); } catch (Exception ex) { _logger.Error(this.GetType(), $"    {coreInitData}", ex); } } }
      
      







メッセージを送信する



この段階で:





C#実装例
 /// <summary> ///       -   /// </summary> /// <typeparam name="TMessageDomain">  </typeparam> /// <typeparam name="TMessageProxy">   </typeparam> /// <typeparam name="TAckProxy">   </typeparam> public class SendMessageCoreService<TMessageDomain, TMessageProxy, TAckProxy> where TMessageDomain : MessageDomain where TAckProxy : IAckRequestAck { private readonly IMessageDomainService<TMessageDomain> _messageDomainService; private readonly IMessageProxyConverter<TMessageDomain, TMessageProxy> _messageProxyConverter; private readonly ISendMessageProxyProvider<TMessageProxy, TAckProxy> _sendMessageProxyProvider; private readonly ISendMessageHandler<TMessageDomain, TAckProxy> _sendMessageHandler; private readonly IGisLogger _logger; public SendMessageCoreService(IMessageDomainService<TMessageDomain> messageDomainService, IMessageProxyConverter<TMessageDomain, TMessageProxy> messageProxyConverter, ISendMessageProxyProvider<TMessageProxy, TAckProxy> sendMessageProxyProvider, ISendMessageHandler<TMessageDomain, TAckProxy> sendMessageHandler, IGisLogger logger) { _messageDomainService = messageDomainService; _messageProxyConverter = messageProxyConverter; _sendMessageProxyProvider = sendMessageProxyProvider; _sendMessageHandler = sendMessageHandler; _logger = logger; } public void SendMessages(CoreInitData coreInitData) { var stopWatch = new Stopwatch(); stopWatch.Start(); try { //     //      //       var messages = _messageDomainService.GetMessageDomainsForSend(coreInitData); foreach (var messageDomain in messages) { try { //        var proxyMessageRequests = _messageProxyConverter.ToMessageProxy(messageDomain); //   var proxyAck = _sendMessageProxyProvider.SendMessage(proxyMessageRequests); //   _sendMessageHandler.SendSuccess(messageDomain, proxyAck); } catch (Exception exception) { //  _sendMessageHandler.SendFail(messageDomain, exception); } } stopWatch.Stop(); _logger.Info(this.GetType(), $" {messages.Count}    {coreInitData.UkId}  " + $"{messages.Count(x => x.Status == MessageStatus.Sent)} , " + $"{messages.Count(x => x.Status == MessageStatus.SendError)}   , " + $"{messages.Count(x => x.Status == MessageStatus.SendErrorTryAgain)}   ,  {stopWatch.Elapsed}"); } catch (Exception ex) { _logger.Error(this.GetType(), $"    {coreInitData}", ex); } } }
      
      







メッセージ処理の結果を取得する



この段階で:





C#実装例
  /// <summary> ///       -    /// </summary> /// <typeparam name="TMessageDomain">  </typeparam> /// <typeparam name="TGetStateResultProxy">     </typeparam> /// <typeparam name="TResultProxy">     </typeparam> /// <typeparam name="TResult">    </typeparam> public class GetResultsCoreService<TMessageDomain, TGetStateResultProxy, TResultProxy, TResult> where TMessageDomain : MessageDomain where TResultProxy : IGetStateResult { private readonly IMessageDomainService<TMessageDomain> _messageDomainService; private readonly IGetResultProxyProvider<TGetStateResultProxy, TResultProxy> _getResultProxyProvider; private readonly IGetStateProxyConverter<TGetStateResultProxy, TMessageDomain> _getStateProxyConverter; private readonly IResultConverter<TResultProxy, TResult> _resultConverter; private readonly ISaveResultService<TResult, TMessageDomain> _saveResultService; private readonly IGetResultMessageHandler<TMessageDomain, TResult> _getResultMessageHandler; private readonly IGisLogger _logger; /// <summary> ///  ,   ,      /// </summary> private const int GET_RESULT_TIMEOUT_IN_DAYS = 3; public GetResultsCoreService(IMessageDomainService<TMessageDomain> messageDomainService, IGetResultProxyProvider<TGetStateResultProxy, TResultProxy> getResultProxyProvider, IGetStateProxyConverter<TGetStateResultProxy, TMessageDomain> getStateProxyConverter, IResultConverter<TResultProxy, TResult> resultConverter, ISaveResultService<TResult, TMessageDomain> saveResultService, IGetResultMessageHandler<TMessageDomain, TResult> getResultMessageHandler, IGisLogger logger) { _messageDomainService = messageDomainService; _getResultProxyProvider = getResultProxyProvider; _getStateProxyConverter = getStateProxyConverter; _resultConverter = resultConverter; _saveResultService = saveResultService; _getResultMessageHandler = getResultMessageHandler; _logger = logger; } public void GetResults(CoreInitData coreInitData) { var stopWatch = new Stopwatch(); stopWatch.Start(); try { //       var messages = _messageDomainService.GetMessageDomainsForGetResults(coreInitData); foreach (var messageDomain in messages) { try { //    getState      var getStateProxy = _getStateProxyConverter.ToGetStateResultProxy(messageDomain); TResultProxy resultProxy; //  . //  false,      // true,      if (_getResultProxyProvider.TryGetResult(getStateProxy, out resultProxy)) { //        -   var result = _resultConverter.ToResult(resultProxy); //    _saveResultService.SaveResult(result, messageDomain); //       _getResultMessageHandler.Success(messageDomain, result); } else { if (messageDomain.SendedDate.HasValue && DateTime.Now.Subtract(messageDomain.SendedDate.Value).Days > GET_RESULT_TIMEOUT_IN_DAYS) { //        ,  _getResultMessageHandler.NoResultByTimeout(messageDomain); } else { //,      _getResultMessageHandler.NotReady(messageDomain); } } } catch (Exception exception) { //     _getResultMessageHandler.Fail(messageDomain, exception); } } stopWatch.Stop(); _logger.Info(this.GetType(), $" {messages.Count}    {coreInitData.UkId}  " + $"{messages.Count(x => x.Status == MessageStatus.Done)}  , " + $"{messages.Count(x => x.Status == MessageStatus.InProcess)}  , " + $"{messages.Count(x => x.Status == MessageStatus.ResponseTakingError)}   , " + $"{messages.Count(x => x.Status == MessageStatus.ResponseTakingErrorTryAgain)}   ,  {stopWatch.Elapsed}"); } catch (Exception ex) { _logger.Error(this.GetType(),$"    {coreInitData}", ex); } } }
      
      







おわりに



非同期相互作用モデルを使用すると、「1つのMessageGUID-1つの完了したアクション」という合意を通じて、GISユーティリティに送信される情報を制御できます。 お勧めです!



githubで 、対話に使用する基本クラス投稿し、最も詳細なコメントを書き込もうとしました。 このアプローチを使用する場合、情報システムからデータを収集し、結果を処理するロジックを実装するだけです。



All Articles