Je travaille actuellement sur une application avec un processus d'inscription. Ce processus d'inscription communiquera, à un moment donné, avec des systèmes externes de manière asynchrone. Pour garder cette question concise, je vous montre deux acteurs importants que j'ai écrit:Acteurs enfants, futures et exceptions
SignupActor.scala
class SignupActor extends PersistentFSM[SignupActor.State, Data, DomainEvt] {
private val apiActor = context.actorOf(ExternalAPIActor.props(new HttpClient))
// At a certain point, a CreateUser(data) message is sent to the apiActor
}
ExternalAPIActor.scala
class ExternalAPIActor(apiClient: HttpClient) extends Actor {
override def preRestart(reason: Throwable, message: Option[Any]) = {
message.foreach(context.system.scheduler.scheduleOnce(3 seconds, self, _))
super.preRestart(reason, message)
}
def receive: Receive = {
case CreateUser(data) =>
Await.result(
apiClient.post(data)
.map(_ => UserCreatedInAPI())
.pipeTo(context.parent),
Timeout(5 seconds).duration
)
}
}
Cette la configuration semble fonctionner comme prévu. Lorsqu'il y a un problème avec l'API externe (par exemple un problème de délai d'attente ou de réseau), le Future
renvoyé par HttpClient::post
échoue et génère une exception grâce à Await.result
. Ceci, à son tour grâce au SupervisorStrategy
de l'acteur parent SignupActor
, va redémarrer le ExternalAPIActor
où nous pouvons renvoyer le dernier message à lui-même avec un petit délai pour éviter les interblocages.
Je vois quelques problèmes avec cette configuration:
- Dans la méthode
receive
deExternalAPIActor
, le blocage se produit. Pour autant que je comprends, le blocage au sein des Acteurs est considéré comme un anti-pattern. - Le délai utilisé pour renvoyer le message est statique. Si l'API est indisponible pour des périodes plus longues, nous continuerons à envoyer des requêtes HTTP toutes les 3 secondes. Je voudrais plutôt une sorte de mécanisme exponentiel de recul ici.
Pour continuer avec ce dernier, je l'ai essayé ce qui suit dans le SignupActor
:
SignupActor.scala
val supervisor = BackoffSupervisor.props(
Backoff.onFailure(
ExternalAPIActor.props(new HttpClient),
childName = "external-api",
minBackoff = 3 seconds,
maxBackoff = 30 seconds,
randomFactor = 0.2
)
)
private val apiActor = context.actorOf(supervisor)
Malheureusement, cela ne semble pas faire quoi que ce soit à tout - la méthode preRestart
de ExternalAPIActor
n'est pas appelée du tout. Lors du remplacement de Backoff.onFailure
par Backoff.onStop
, la méthode est appelée, mais sans aucun type de remise exponentielle du tout.
Compte tenu de ce qui précède, mes questions sont les suivantes:
- Est-ce en utilisant
Await.result
le recommandé (la seule?) Façon de vous assurer les exceptions lancées dans unFuture
retour des services appelés au sein des acteurs sont capturés et manipulés en conséquence ? Une partie particulièrement importante de mon cas d'utilisation particulier est le fait que les messages ne doivent pas être supprimés mais retentés lorsque quelque chose ne va pas. Ou existe-t-il une autre manière (idiomatique) de gérer les exceptions dans des contextes asynchrones dans Actors? Comment utiliser leBackoffSupervisor
comme prévu dans ce cas? Encore une fois: il est très important que le message responsable de l'exception ne soit pas abandonné, mais réessayé jusqu'à un nombre N fois de fois (à déterminer par l'argumentmaxRetries
deSupervisorStrategy
.
J'aime le titre. – Beta