Manejo de Errores
Los errores son una consecuencia inevitable al procesar mensajes de sistemas externos a través de redes poco fiables. La biblioteca de connector intenta mitigarlos con estrategias de manejo de errores, que difieren según la naturaleza y el contexto del error en cuestión.
El principal factor que distingue cómo se manejan los errores es si el error ocurre mientras se envía o se recibe.
Manejo de errores en Send Connectors
El manejo de errores en los send connectors se centra principalmente en manejar casos donde un mensaje no logra ser entregado sobre el transport. Esta clase de errores es de naturaleza transitoria. Para superar estos errores, el send connector utiliza la librería resilience4j para envolver llamadas con circuit breaking, fallback/routing y reintentos, todo lo cual puede configurarse. Consulte la documentación Resilient Message Sending para una explicación más detallada de cómo funciona.
Si un mensaje no logra enviarse, a pesar de reintentos y otras estrategias de resiliencia, entonces la entrega del mensaje completa excepcionalmente y el emisor debe manejar la excepción por sí mismo, ya que el connector no puede hacer más sin conocer la aplicación cliente.
Manejo de errores en Receive Connectors
Pueden producirse excepciones durante la recepción de un mensaje, como por ejemplo no lograr deserializar un TransportMessage al tipo de destino.
Todos los mensajes fallidos se agregan a un deadletter configurable; de este modo, el mensaje fallido puede ser reconocido (ack) y el procesamiento puede continuar.
Deadletter Appender
DeadletterAppender es una interfaz funcional que se invoca cada vez que un mensaje falla durante el procesamiento.
@FunctionalInterface
public interface DeadletterAppender {
CompletionStage<Void> append(ReceiveConnectorException receiveConnectorException);
}
Proporcionar una implementación de DeadletterAppender es opcional y, si no se proporciona, el connector usará la implementación predeterminada. El comportamiento predeterminado es simplemente registrar tanto el mensaje fallido como la excepción que causó el error.
Todos los mensajes fallidos se proporcionarán como una excepción que extiende ReceiveConnectorException. ReceiveConnectorException envuelve la excepción original como la causa junto con el mensaje recibido.
public class ReceiveConnectorException extends RuntimeException {
private final ReceivedMessage<? extends ConnectorMessage<?>> receivedMessage;
public ReceiveConnectorException(ReceivedMessage<? extends ConnectorMessage<?>> receivedMessage, Throwable e) {
super(e);
this.receivedMessage = receivedMessage;
}
public ReceiveConnectorException(ReceivedMessage<? extends ConnectorMessage<?>> receivedMessage, String message) {
super(message);
this.receivedMessage = receivedMessage;
}
}
Clasificación de Excepciones
Hay algunas excepciones que ocurren debido a fallos transitorios, y para estas puede tener sentido intentar recuperarse. Sin embargo, dado que la mayor parte de la funcionalidad del connector se basa en pasar objetos inmutables a través de funciones, podemos evitar intentar reintentar operaciones fallidas y avanzar lo más rápido posible ya que el resultado no debería cambiar.
Para tener en cuenta ambas situaciones, clasificamos las excepciones como recuperables o irrecuperables.
Las excepciones pueden clasificarse como irrecuperables extendiendo UnrecoverableReceiveConnectorException, una clase marcador que a su vez extiende la clase base ReceiveConnectorException.
De lo contrario, para excepciones recuperables, extienda ReceiveConnectorException y agregue la excepción a la clave recoverable-exceptions en la configuración.
recoverable-exceptions es una lista de objetos RecoverableExceptionFilter que constan de lo siguiente:
| Cuando se lanza una excepción, se escanea toda la traza de pila en busca de excepciones Recoverable y, si se encuentran, entonces la excepción se clasifica como Recoverable. |
| key | definition | example |
|---|---|---|
|
Expresión regular para hacer match contra el nombre completo de la excepción (fully qualified), p. ej. HttpErrors$HttpServerErrorException |
".*\\$HttpServerErrorException" |
|
Lista de pares clave-valor, todos los cuales deben coincidir además del filtro para que la excepción se clasifique como recuperable. |
[{"statusCode": "503 Service Unavailable"}] |
La configuración predeterminada se incluye a continuación:
recoverable-exceptions = [
{
filter = ".*\\$HttpServerErrorException"
properties = [{"statusCode": "503 Service Unavailable"}]
},
{
filter = ".*\\.ReceiveConnectorException"
}
]
Acknowledgements
Dependiendo de la implementación del transport, los acknowledgements pueden necesitar ser realizados por el receiving connector. Después de manejar un error, el receiving connector llamará al método acknowledge en el transport para que el procesamiento pueda continuar.
Manejadores de Errores
Para cada etapa, podemos proporcionar opcionalmente una función que tome un ReceiveConnectorException y devuelva un CompletionStage<Void>.
public DefaultMapToConnectorMessageStage(ReceiveTransportMessageConverter<T> transportMessageConverter,
Integer parallelism,
ExecutionContextExecutor dispatcher,
ReceiveErrorHandler doOnError) {
if (transportMessageConverter == null) {
throw new IllegalArgumentException("'transportMessageConverter' must not be null");
}
if (dispatcher == null) {
throw new IllegalArgumentException("'dispatcher' must not be null");
}
this.transportMessageConverter = transportMessageConverter;
this.parallelism = Objects.requireNonNullElseGet(parallelism, () -> {
final int processors = Runtime.getRuntime().availableProcessors();
log.debug("Using default parallelism of {}", processors);
return processors;
});
this.dispatcher = dispatcher;
this.doOnError = doOnError;
}
Esto facilita agregar manejo de errores específico para cada etapa.
También podemos proporcionar el parámetro doOnError al ReceiveConnector, que se invocará en cada ReceiveConnectorException además de al manejador de errores de la etapa.