ametiste
ametiste

Reputation: 385

Error message handling behaviour, spring integration

Im having 2 outbound gateways with similar but not equal data and format. I'd want to use one as alternative in case 1st doesn't reply or reply too long. Also in future I want to have option to choose which one to get data from.(So Id like to have it as separate outbound gateway, I saw examples with retry advices and would like to avoid this variant)

Atm at very simplified schema I use error channel as incoming channel of alternative gateway. Works perfect until alternative gateway fails too, then I receive:

org.springframework.integration.MessagingException: failure occurred in error-handling flow

There's a real reason of this (connection timeout or any) in stacktrace, but it seems not satisfying for me, since i'd like to handle some exception types differently. I'd like to get error message (and exception) from alternative gateway, not wrapped one.

Is there any way to get it right or Im totally wrong in architecture of this?

Upvotes: 1

Views: 5023

Answers (2)

ametiste
ametiste

Reputation: 385

found a solution that suits me, not sure its proper one, but for now it satisfies all suggested future requirements. I separated my outbound gateways to 2 segments with independent error channels, where error channel of first is input of second. I can process errors from both outbounds, route exceptions and as seems to me easily add some logic futher. In case of both outbound gateway fails there will be only last gateway exception recieved but that suits me since i dont need to aggregate those errors. More suggestions are gladly accepted of course.

app-config.xml looks like this

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:elasticsearch="http://www.pilato.fr/schema/elasticsearch"
    xmlns:int-http="http://www.springframework.org/schema/integration/http"
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-3.0.xsd
        http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-3.0.xsd
        http://www.pilato.fr/schema/elasticsearch http://www.pilato.fr/schema/elasticsearch/elasticsearch-0.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">


    <int-http:inbound-gateway id="inboundController"
        request-channel="requests"
        reply-channel="replies-f"
        path="/item_info"
        supported-methods="GET"
        payload-expression="#requestParams"
    >
        <int-http:request-mapping 
            params="id"/>
    </int-http:inbound-gateway>


    <int-http:outbound-gateway id="simpleGateway"
                     request-channel="outboundRequests"
                     reply-channel="replies-f"
                     http-method="GET"
                     expected-response-type="com.dph.iteminfo.transformer.ItemInfoDTO"
                     url="http://10.5.4.134:8080/ess/iteminfo?id={itemId}&amp;group_size={groupSize}"

                     >
            <int-http:uri-variable name="itemId" expression="payload.get('id')[0]"/>        
            <int-http:uri-variable name="groupSize" expression="payload.containsKey('group_size') ? payload.get('group_size')[0] : 5"/>
    </int-http:outbound-gateway>

   <int-http:outbound-gateway id="alternativeSource"
                request-channel="altOutboundChannel"
                reply-channel="replies-f"
                http-method="GET"
                expected-response-type="java.lang.String"
                url="http://10.5.4.134:8080/ess/iteminfo?id={itemId}&amp;len={groupSize}"
               >
            <int-http:uri-variable name="itemId" expression="payload.get('id')[0]"/>
            <int-http:uri-variable name="groupSize" expression="payload.containsKey('group_size') ? payload.get('group_size')[0] : 5"/>
    </int-http:outbound-gateway>

    <int:service-activator input-channel="requests" ref="outboundMain"/>
    <int:gateway id="outboundMain" default-request-channel="outboundRequests" error-channel="altChannel" />

    <int:exception-type-router default-output-channel="replies-f" input-channel="altChannel">
        <int:mapping exception-type="org.springframework.web.client.HttpServerErrorException" channel="resendableMessagesChannel"/>
        <int:mapping exception-type="org.springframework.web.client.ResourceAccessException" channel="resendableMessagesChannel"/>
    </int:exception-type-router>

     <int:service-activator input-channel="resendableMessagesChannel" ref="resender"/>
     <int:gateway id="resender" default-request-channel="processedErrorsChannel" error-channel="finalErrors"/>

    <int:transformer input-channel="processedErrorsChannel" output-channel="altOutboundChannel" expression="payload.getFailedMessage().getPayload()" /> 


    <int:bridge input-channel="finalErrors" output-channel="replies-f"/>
    <!-- will be error processing here -->

    <int:channel id="finalErrors" />
    <int:channel id="requests" />
    <int:channel id="replies-f" />

</beans>

Upvotes: 1

Artem Bilan
Artem Bilan

Reputation: 121262

Looks like you want to achieve a failover pattern. For this purpose you can simply subscribe both endpoints to the same channel, but without load-balancing:

<channel id="input">
    <dispatcher load-balancer="none"/>
</channel>

<service-activator input-channel="input" ref="service1"/>

<service-activator input-channel="input" ref="service2"/>

In this case, if the first <service-activator> will fail by some reason, the Message will be delivered to the second one.

And there is no reason to use retry, or try to reestablish flow from error handling.

UPDATE:

To skip some exceptions and do failover to the next subscriber you can use some custom RequestHandlerAdvice(Expression Evaluating Advice):

class ExceptionProviderRequestHandlerAdvice extends ExpressionEvaluatingRequestHandlerAdvice {

@Override
protected Object doInvoke(AbstractRequestHandlerAdvice.ExecutionCallback callback, Object target, Message<?> message) throws Exception {
    def result = super.doInvoke(callback, target, message)
    if (result in Exception) {
        throw result
    }
    return result
}

}

And your onFailureExpression should decide: to return exception for rethrow or not:

<int:request-handler-advice-chain>
    <beans:bean class="com.cs.otppr.core.aop.ExceptionProviderRequestHandlerAdvice">
        <beans:property name="returnFailureExpressionResult" value="true"/>
        <beans:property name="onFailureExpression"
           value="#exception instanceof T (com.my.proj.MyException) ? #exception : null"/>
    </beans:bean>
</int:request-handler-advice-chain>

Or, of course, you can try to find solution on your own...

Upvotes: 3

Related Questions