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 conectores intenta mitigar estos problemas con estrategias de manejo de errores, las cuales difieren dependiendo de la naturaleza y el contexto del error en cuestión.

El principal factor diferenciador en cómo se manejan los errores es si el error ocurre al enviar o al recibir.

Manejo de Errores en Send Connector s

Manejo de errores en el send connector s se centra principalmente en el manejo de casos en los que un mensaje no se entrega a través del transporte. Esta clase de error es transitoria por naturaleza. Para superar estos errores, el send connector utiliza la biblioteca resilience4j para envolver llamadas con interrupción de circuito, recuperación/ruteo y reintentos, todos los cuales pueden ser configurados. Consulte el Envío de Mensajes Resiliente documentación para una explicación más detallada de cómo funciona esto.

Si un mensaje no se envía, a pesar de los reintentos y otras estrategias de resiliencia, entonces la entrega del mensaje se completa de manera excepcional y el remitente debe manejar la excepción por sí mismo, ya que no hay nada más que el conector pueda hacer sin conocer la aplicación cliente.

Manejo de Errores en Receive Connector s

Las excepciones pueden ocurrir durante la recepción de un mensaje, como el fallo al deserializar un TransportMessage al tipo de destino, por ejemplo.

Todo failed los mensajes se añaden a un deadletter configurable, de esta manera el failed el mensaje puede ser reconocido y el procesamiento puede continuar.

Appender de cartas muertas

El DeadletterAppender es una interfaz funcional que se llama siempre que un mensaje falla durante el procesamiento.

@FunctionalInterface
public interface DeadletterAppender {

    /**
     * Appends the {@link ReceiveConnectorException} to the deadletter queue.
     *
     * @param receiveConnectorException contains the failed {@link ReceivedMessage message} and the cause of failure
     * @return a {@link CompletionStage}
     */
    CompletionStage<Void> append(ReceiveConnectorException receiveConnectorException);
}

Proporcionando un DeadletterAppender La implementación es opcional y, si no se proporciona una, el conector utilizará la implementación predeterminada. El comportamiento predeterminado es simplemente registrar tanto el failed mensaje y la excepción que causó el error.

Todo failed Los mensajes 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 ResolvableRootCauseException {
    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;
    }

    public ProcessingContext processingContext() {
        return Optional
                .ofNullable(receivedMessage)
                .map(ReceivedMessage::getMessage)
                .map(ConnectorMessage::getProcessingContext)
                .orElse(null);
    }
}

Clasificación de Excepciones

Existen algunas excepciones que ocurren debido a fallos transitorios, y para estas, puede tener sentido intentar recuperar. Sin embargo, dado que la mayoría de la funcionalidad del conector 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 nunca debería cambiar.

Para tener en cuenta ambas situaciones, clasificamos las excepciones como recuperables o irrecuperables. Las excepciones pueden clasificarse como irrecuperables extendiendo UnrecoverableReceiveConnectorException, un marker clase que a su vez extiende la clase de excepción base ReceiveConnectorException. De lo contrario, para las excepciones que son recuperables, extienda ReceiveConnectorException y añada la excepción a la recoverable-exceptions clave en la configuración.

`recoverable-exceptions`es una lista de objetos RecoverableExceptionFilter que consiste en lo siguiente:

Cuando se lanza una excepción, se escanea toda la traza de la pila en busca de excepciones recuperables y, si se encuentran, la excepción se clasifica como recuperable.
clave definición ejemplo

filter

Expresión regular para coincidir con el nombre de excepción completamente calificado, por ejemplo. HttpErrors$HttpServerErrorException

".*\\$HttpServerErrorException "

properties

Lista de pares clave-valor, todos los cuales deben coincidir además del filtro para que la excepción se clasifique como una excepción 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"
  }
]

Agradecimientos

Dependiendo de la implementación del transporte, pueden ser necesarios los acuses de recibo por parte del conector receptor. Después de manejar un error, el conector receptor llamará al método de reconocimiento en el transporte para que el procesamiento pueda continuar.

Manejadores de Errores

Para cada etapa, podemos opcionalmente proporcionar una función que toma un ReceiveConnectorException y devuelve 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 la adición de manejo de errores específico para cada etapa. También podemos proporcionar el doOnError parámetro al ReceiveConnector, que se llamará en cada ReceiveConnectorException en adición a el controlador de errores de etapa.