If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNuestro nuevo y brillante transporte external_messages
lee los mensajes de esta colamessages_from_external
, que fingimos que está siendo rellenada por una aplicación externa. Tomamos este JSON y, enExternalJsonMessengerSerializer
, lo descodificamos, creamos el objeto LogEmoji
, lo ponemos en Envelope
, incluso le añadimos un sello, y finalmente lo devolvemos, para que pueda ser enviado de nuevo a través del sistema de bus de mensajes.
¡Esto tiene muy buena pinta! Pero hay dos mejoras que quiero hacer. En primer lugar, no hemos codificado de forma muy defensiva. Por ejemplo, ¿qué pasa si, por alguna razón, el mensaje contiene JSON no válido? Comprobemos eso: si null === $data
, entonces lanza un new MessageDecodingFailedException('Invalid JSON')
... lines 1 - 6 | |
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; | |
... lines 8 - 10 | |
class ExternalJsonMessageSerializer implements SerializerInterface | |
{ | |
public function decode(array $encodedEnvelope): Envelope | |
{ | |
... lines 15 - 19 | |
if (null === $data) { | |
throw new MessageDecodingFailedException('Invalid JSON'); | |
} | |
... lines 23 - 34 | |
} | |
... lines 36 - 40 | |
} |
Te mostraré por qué usamos exactamente esta clase de excepción dentro de un minuto. Pero probemos esto con algún JSON no válido y... veamos qué ocurre. Ve a reiniciar el trabajador para que vea nuestro nuevo código:
php bin/console messenger:consume -vv external_messages
Luego, en el gestor RabbitMQ, vamos a cometer un error JSON muy molesto: añadir una coma después de la última propiedad. ¡Publica ese mensaje! Bien, muévete y... ¡explosión!
MessageDecodingFailedException: JSON inválido
Ah, y es interesante: ¡esto mató a nuestro proceso de trabajo! Sí, si se produce un error durante el proceso de descodificación, la excepción mata a tu trabajador. No es lo ideal... pero en realidad... no es un problema. En producción, ya estarás utilizando algo como el supervisor, que reiniciará el proceso cuando muera.
Añadamos código para comprobar un posible problema diferente: comprobemos si falta esta clave emoji
: si no isset($data['emoji'])
, esta vez lanza una excepción normal: throw new \Exception('Missing the emoji key!')
.
... lines 1 - 10 | |
class ExternalJsonMessageSerializer implements SerializerInterface | |
{ | |
public function decode(array $encodedEnvelope): Envelope | |
{ | |
... lines 15 - 23 | |
if (!isset($data['emoji'])) { | |
throw new \Exception('Missing the emoji key!'); | |
} | |
... lines 27 - 34 | |
} | |
... lines 36 - 40 | |
} |
Bien, pasa y reinicia el trabajador:
php bin/console messenger:consume -vv external_messages
De nuevo en Rabbit, elimina la coma extra y cambia emoji
por emojis
. Publica! En el terminal... ¡genial! ¡Ha explotado! Y aparte de la clase de excepción... parece idéntico al fallo que vimos antes:
Excepción: ¡Falta la tecla emoji!
Pero... acaba de ocurrir algo diferente. Intenta volver a ejecutar el trabajador:
php bin/console messenger:consume -vv external_messages
¡Woh! ¡Ha explotado! Falta la tecla emoji. Ejecútalo de nuevo:
php bin/console messenger:consume -vv external_messages
¡El mismo error! Ésta es la diferencia entre lanzar unException
normal en el serializador y el especial MessageDecodingFailedException
. Cuando lanzas un MessageDecodingFailedException
, tu serializador está diciendo básicamente:
¡Oye! Algo ha ido mal... y quiero lanzar una excepción. Pero, creo que deberíamos descartar este mensaje de la cola: no tiene sentido de intentarlo una y otra vez. ¡Kthxbai!
Y eso es súper importante. Si no descartamos este mensaje, cada vez que nuestro trabajador se reinicie, fallará con ese mismo mensaje... una y otra vez... para siempre. Cualquier mensaje nuevo empezará a acumularse detrás de él en la cola.
Así que cambiemos el Exception
por MessageDecodingFailedException
. Pruébalo ahora:
... lines 1 - 10 | |
class ExternalJsonMessageSerializer implements SerializerInterface | |
{ | |
public function decode(array $encodedEnvelope): Envelope | |
{ | |
... lines 15 - 23 | |
if (!isset($data['emoji'])) { | |
throw new MessageDecodingFailedException('Missing the emoji key!'); | |
} | |
... lines 27 - 34 | |
} | |
... lines 36 - 40 | |
} |
php bin/console messenger:consume -vv external_messages
Explotará la primera vez... pero el MessageDecodingFailedException
debería haberlo eliminado de la cola. Cuando ejecutemos el trabajador ahora:
php bin/console messenger:consume -vv external_messages
¡Sí! El mensaje ha desaparecido y la cola está vacía.
A continuación, vamos a añadir un superpoder más a este serializador. ¿Qué pasa si ese sistema externo envía a nuestra aplicación muchos tipos diferentes de mensajes: no sólo un mensaje para registrar emojis, sino quizá también mensajes para borrar fotos o cocinar una pizza? ¿Cómo puede nuestro serializador averiguar qué mensajes son cada uno... y qué objeto de mensaje debe crear?
Hey SWORP!
Yea, that makes sense :). Have you tried the WorkerMessageFailedEvent
event? It's triggered each time a message fails while being processed by the worker.
Cheers!
So the message is removed from the queue, and lost ? But if it's our fault ? I mean we didn't implemented yet. Could we leave the message but at the end of the queue ? or with a low priority. So we could implement it and then get it ?
Hey Julien,
I suppose failure transport is what you need here: https://symfonycasts.com/sc... . The idea is to retry a few times and if it still fails - moved that message to the failure transport where you will be able to retry it whenever you're ready.
I hope this helps!
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // v1.8.0
"doctrine/doctrine-bundle": "^1.6.10", // 1.11.2
"doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
"doctrine/orm": "^2.5.11", // v2.6.3
"intervention/image": "^2.4", // 2.4.2
"league/flysystem-bundle": "^1.0", // 1.1.0
"phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.1
"sensio/framework-extra-bundle": "^5.3", // v5.3.1
"symfony/console": "4.3.*", // v4.3.2
"symfony/dotenv": "4.3.*", // v4.3.2
"symfony/flex": "^1.9", // v1.18.7
"symfony/framework-bundle": "4.3.*", // v4.3.2
"symfony/messenger": "4.3.*", // v4.3.4
"symfony/property-access": "4.3.*", // v4.3.2
"symfony/property-info": "4.3.*", // v4.3.2
"symfony/serializer": "4.3.*", // v4.3.2
"symfony/validator": "4.3.*", // v4.3.2
"symfony/webpack-encore-bundle": "^1.5", // v1.6.2
"symfony/yaml": "4.3.*" // v4.3.2
},
"require-dev": {
"easycorp/easy-log-handler": "^1.0.7", // v1.0.7
"symfony/debug-bundle": "4.3.*", // v4.3.2
"symfony/maker-bundle": "^1.0", // v1.12.0
"symfony/monolog-bundle": "^3.0", // v3.4.0
"symfony/stopwatch": "4.3.*", // v4.3.2
"symfony/twig-bundle": "4.3.*", // v4.3.2
"symfony/var-dumper": "4.3.*", // v4.3.2
"symfony/web-profiler-bundle": "4.3.*" // v4.3.2
}
}
Is there a way to handle all the exception from Messages the way it is done by Subscribers to
KernelEvents::EXCEPTION
orExceptionListener->onKernelException
?Seems like exceptions occurring when processing messages is some separate event , other than
KernelEvents::EXCEPTION
.For example, need to send email to admin, in case some problem occurs with the queue.
Thanks!