Patterns - Invoke/retry

Vue d'ensemble

Le modèle Invoke/retry permet aux services d'invoquer d'autres services, en répétant l'invocation si elle échoue initialement ou ultérieurement. S'il est exécuté en arrière-plan, il est possible de fournir un service de rappel qui sera invoqué si le service cible répond finalement ou s'il ne répond jamais.

Il existe trois modes de fonctionnement :

  • Invoquer un service de manière synchrone et si l'appel échoue, répéter l'appel un nombre configuré de fois en bloquant toujours le service émettant l'appel - tous les appels sont bloquants.

  • Appeler un service de manière synchrone mais, en cas d'échec, répéter l'appel en arrière-plan en invoquant un callback lorsque le service cible répond enfin - seul le premier appel est bloquant.

  • Appeler un service de manière asynchrone immédiatement, en répétant l'appel en arrière-plan s'il échoue la première fois et les fois suivantes - aucun des appels n'est bloquant.

Mode d'appel Invocation initiale Autres tentatives
Sync + sync Blockant Blockant
Sync + async Blockant En arrière-plan
Async + async En arrière-plan En arrière-plan

Notez que, quel que soit le mode d'utilisation, le mécanisme d'invoke/retry n'est opérationnel que si le service cible lève une exception - toute sous-classe de l'exception intégrée de Python sera suffisante. Sans exception levée pour signaler que quelque chose a mal tourné, il n'y aura pas d'invocations répétées.

Exemples d'utilisation et API

Mode sync + sync

Invoque un service et répète l'exécution un nombre donné de fois en bloquant le service appelant si le service cible ne répond pas. Au final, soit une ZatoException est levée par le modèle, soit une réponse est produite.

from zato.common.exception import ZatoException
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        try:

            # The service to invoke
            target = 'my.retry.target'

            # How many times to invoke it and how long to sleep between the calls
            config = {
              'repeats': 3,
              'seconds': 1
            }

            # Call invoke/retry
            response = self.patterns.invoke_retry.invoke(target, **config)

            # Everything went fine in the end
            self.logger.info('Response received %r', response)

        # We enter here if all the invocations ended in an exception
        except ZatoException as e:
            self.logger.warn('Caught an exception %s', e)

Mode synchrone + asynchrone

Invoque un service de manière bloquante et réessaie en arrière-plan si l'invocation échoue. Renvoie l'ID de corrélation de l'appel en arrière-plan afin que les demandes puissent être corrélées avec les réponses. Une callback sera invoquée avec soit la réponse de la cible, soit l'information qu'elle n'a pas pu être invoquée.

from zato.server.service import Service

class MyService(Service):

    def handle(self):

        # The service to invoke
        target = 'my.retry.target'

        # How many times to invoke and how long to sleep between the calls,
        # also a flag indicating that should the initial call fail, it will be
        # repeated in async next on. Finally, a callback to invoke should async be used
        # and a dictionary of context the callback will receive (can be omitted).
        config = {
            'repeats': 3,
            'seconds': 1,
            'async_fallback':True,
            'callback': 'my.retry.callback',
            'context': {'hi':'there'},
        }

        # Call invoke/retry and obtain a Correlation ID
        cid = self.patterns.invoke_retry.invoke(target, **config)

        # Can be used for correlating requests and responses
        self.logger.info('CID %s', cid)

Mode asynchrone + asynchrone

Invoque un service en arrière-plan et, si l'appel échoue, continue à l'invoquer de manière asynchrone. Ne bloque jamais le service appelant qui reçoit l'ID de corrélation de l'appel invoke/retry à utiliser pour corréler la demande émise avec la réponse que le callback obtient (le cas échéant). Une fois que la réponse est produite par la cible, un callback est invoqué. Sinon, si la cible ne répond jamais par un message autre qu'un message d'erreur, le callback reçoit une information le précisant.

from zato.server.service import Service

class MyService(Service):

    def handle(self):

        # The service to invoke
        target = 'my.retry.target'

        # How many times to invoke and how long to sleep between the calls,
        # also a callback to invoke and a dictionary of context
        # the callback will receive (can be omitted).
        config = {
            'repeats': 3,
            'seconds': 1,
            'callback': 'my.retry.callback',
            'context': {'hi':'there'},
        }

        # Call invoke/retry and obtain a Correlation ID
        cid = self.patterns.invoke_retry.invoke_async(target, **config)

          # Can be used for correlating requests and responses
          self.logger.info('CID %s', cid)

Création de callbacks

Le callback Invoke/retry est un service qui dans son attribut self.request.payload recevra en entrée un dictionnaire Python du format suivant. Le callback sera invoqué peu importe si la cible a répondu avec succès ou non.

  {
    'ok': False,
    'source': 'retry.my-service',
    'target': 'my.retry.target',
    'retry_repeats': 3,
    'retry_seconds': 1,
    'call_cid': 'e00fc20637b8aa2377ab3ef2',
    'orig_cid': '2564ef5c0ec8aa2302af33a2',
    'req_ts_utc': '2025-01-21T20:25:25.446454+00:00',
    'context': {'hi': 'there'},
  }
Clé Notes
ok Vrai/Faux, indiquant si la cible a produit une réponse.
source Service appelant
target Service appelé
retry_repeats Combien de fois faut-il réessayer l'invocation si elle a échoué la première fois
retry_seconds Temps d'attente entre les tentatives
call_cid La cible d'ID de corrélation avec laquelle a été appelé
orig_cid La source de l'ID de corrélation avec laquelle a été invoquée à l'origine
req_ts_utc Quand la cible a été invoquée, en UTC
context Un dictionnaire de données arbitraires définies par l'utilisateur, passées de la source au callback.

Spécification des intervalles

Notez que le temps d'attente entre les tentatives peut toujours être exprimé à la fois en minutes et en secondes et au moins l'un d'entre eux doit être fourni en entrée. En interne, les minutes seront converties en secondes.