⬆️ ⬇️

GIS utilities: asynchronous model of interaction

I continue to share experiences on interaction with GIS utilities. The next task, after installing a secure connection , was the organization of messaging. The developers of GIS utilities offer two models of interaction: synchronous and asynchronous. Some developers choose the synchronous model because of its simplicity and accessibility. In this article I will try to explain why it is necessary to use the asynchronous model and give hints on the implementation in C #.



Selection of interaction model



The synchronous model implies the classic "request-response". You form the request object, send it to the GIS utilities, and the system keeps the connection until the answer is generated or the connection does not break on timeout.



The asynchronous model is based on two operations: setting the task to execute the action and querying the status of the execution of the action (if necessary, repeat several times). This model of interaction was called by developers of GIS utilities as preferred and recommended. Next we look at these two operations in detail.



Setting a task to perform an action



To receive information or send data to the GIS utilities, you must fill in the request object. It is there that the information “for which house you need to get information” or “which personal accounts you need to create in the GIS utilities” is located.

')

The message also indicates the message identifier (MessageGUID), it uniquely identifies the message in the information system. A request with the same MessageGUID can be sent several times to the GIS utilities, and the GIS utilities ensures that it is executed once . For example, when setting a task for creating personal accounts, the request fell due to timeout or the connection was suddenly interrupted, we can then send the same request again with the confidence that we will not create unnecessary personal accounts.



If you are sending an array of objects, each object is assigned a TransportGUID, it allows you to match the request and response objects. For example, from the result of processing we will be able to find out why exactly this personal account from the entire message is not accepted by the GIS utilities.



In response, we will receive another MessageGUID - request identifier in the GIS utilities. It is for this identifier that the request execution status will be requested in the GIS utilities.



Query the status of the action



To get the execution status, you need to run a short getStateResult request. It specifies the MessageGUID assigned in the GIS utilities. This request can be sent several times.



In response, we get the message processing status: accepted, in processing, ready. If the message is processed, the GIS utilities return either the result of processing (the received object or information about sending data), or reports errors that occurred while processing the message. The most common errors: “ EXP001000: Internal error. "," Access denied for the data provider organization "*" authority "*" "," The remote server returned an unexpected response: (502) Bad Gateway. "," Listening to api.dom.gosuslugi.ru * did not fulfill any endpoint that could receive the message. " And so on. In fact, they divide messages into two types: those that can be sent again and those that no longer need to resend. For myself, we decided that if we receive a processed GIS utilities error, we do not send the request again. If the request fell off by timeout or a problem in our code, send the message again.



Typical errors GIS utilities



  1. The processing of the message on the side of the GIS utilities is not in the transaction.

    For example, we send a message to create 30 personal accounts, as a result we get “ EXP001000: Internal error. ". We rightly expect that none of the personal accounts has been created, but during the control check we see that ALL personal accounts have been created.



  2. Some requests “hang” in the status “accepted” or “in processing” for an indefinite time.

    Usually, the result of processing a message can be received in a few seconds, but some messages hang in the unprocessed status for several days; such messages should be marked on their side in order not to ask the processing status again.


Due to these "features", the process of sending information to the GIS utilities is divided into three stages:



  1. We load information from the GIS housing and communal services for verification of a current state
  2. Sending the necessary information to the GIS housing and communal services
  3. A check check of the downloaded information, maybe something fell again with “ EXP001000: Internal error. "And the information was created.


The technical side of the interaction



Any process of interaction with GIS utilities consists of three stages:



  1. Getting information for messages, saving it in the database
  2. Creating proxy objects for GIS utilities (remember, we work through WCF), sending a message, processing a response, saving MessageGUID GIS utilities
  3. Getting the result of processing, processing the result


Posting



For each type of interaction, we create a table in the database, for example, ExportHouseInfoMessages or ImportLsMessages, in it we store all the necessary information to create a proxy message object that will be sent to the GIS utilities. It is at this stage that we create MessageGuid messages.



At this stage, you need to create messages only for those data for which no messages have been created yet or the result of processing has been received, otherwise you can duplicate the data.



The main difficulty of this stage is to get the data necessary for sending from its information system. For example, in GIS utilities need to send all the changes in personal accounts, and we did not have a history of changes in the right section.



C # implementation example
/// <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); } } } 




Sending messages



At this stage:





C # implementation example
 /// <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); } } } 




Getting the result of processing the message



At this stage:





C # implementation example
  /// <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); } } } 




Conclusion



The asynchronous interaction model allows you to control the information sent to the GIS utilities through the agreement "one MessageGUID - one completed action." Recommend!



On github laid out the base classes that we use for interaction, tried to write the most detailed comments. If you use this approach, it remains only to implement the logic of lifting data from your information system and process the result.

Source: https://habr.com/ru/post/316362/



All Articles