Reputation: 385
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
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}&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}&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
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