279 lines
18 KiB
XML
279 lines
18 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="gateway"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Inbound Messaging Gateways</title>
|
||
|
||
<section id="gateway-proxy">
|
||
<title>GatewayProxyFactoryBean</title>
|
||
<para>
|
||
Working with Objects instead of Messages is an improvement. However, it would be even better to have no
|
||
dependency on the Spring Integration API at all - including the gateway class. For that reason, Spring
|
||
Integration also provides a <classname>GatewayProxyFactoryBean</classname> that generates a proxy for
|
||
any interface and internally invokes the gateway methods shown below.
|
||
<programlisting language="java"><![CDATA[
|
||
package org.cafeteria;
|
||
|
||
public interface Cafe {
|
||
|
||
void placeOrder(Order order);
|
||
|
||
}
|
||
]]></programlisting>
|
||
|
||
Namespace support is also
|
||
provided which allows you to configure such an interface as a service as demonstrated by the following example.
|
||
<programlisting language="xml"><![CDATA[<gateway id="cafeService"
|
||
service-interface="org.cafeteria.Cafe"
|
||
default-request-channel="requestChannel"
|
||
default-reply-channel="replyChannel"/>]]></programlisting>
|
||
|
||
With this configuration defined, the "cafeService" can now be injected into other beans, and the code that invokes the methods on that
|
||
proxied instance of the Cafe interface has no awareness of the Spring Integration API. The general
|
||
approach is similar to that of Spring Remoting (RMI, HttpInvoker, etc.). See the "Samples" Appendix for
|
||
an example that uses this "gateway" element (in the Cafe demo).
|
||
</para>
|
||
<para>
|
||
The reason that the attributes on the 'gateway' element are named 'default-request-channel' and
|
||
'default-reply-channel' is that you may also provide per-method channel references by using the
|
||
@Gateway annotation.
|
||
<programlisting language="java"><![CDATA[ public interface Cafe {
|
||
|
||
@Gateway(requestChannel="orders")
|
||
void placeOrder(Order order);
|
||
|
||
}]]></programlisting>
|
||
You may alternatively provide such content in <code>method</code> sub-elements if you prefer XML configuration (see the next paragraph).
|
||
</para>
|
||
<para>
|
||
It is also possible to pass values to be interpreted as Message headers on the Message
|
||
that is created and sent to the request channel by using the @Header annotation:
|
||
<programlisting language="java"><![CDATA[ public interface FileWriter {
|
||
|
||
@Gateway(requestChannel="filesOut")
|
||
void write(byte[] content, @Header(FileHeaders.FILENAME) String filename);
|
||
|
||
}]]></programlisting>
|
||
</para>
|
||
|
||
<para>
|
||
If you prefer the XML approach of configuring Gateway methods, you can provide <emphasis>method</emphasis> sub-elements
|
||
to the gateway configuration.
|
||
<programlisting language="xml"><![CDATA[<si:gateway id="myGateway" service-interface="org.foo.bar.TestGateway"
|
||
default-request-channel="inputC">
|
||
<si:method name="echo" request-channel="inputA" reply-timeout="2" request-timeout="200"/>
|
||
<si:method name="echoUpperCase" request-channel="inputB"/>
|
||
<si:method name="echoViaDefault"/>
|
||
</si:gateway>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
You can also provide individual headers per method invocation via XML.
|
||
This could be very useful if the headers you want to set are static in nature and you don't want
|
||
to embed them in the gateway's method signature via <classname>@Header</classname> annotations.
|
||
For example, in the Loan Broker example we want to influence how aggregation of the Loan quotes
|
||
will be done based on what type of request was initiated (single quote or all quotes). Determining the
|
||
type of the request by evaluating what gateway method was invoked, although possible would
|
||
violate the separation of concerns paradigm (the method is a java artifact), but expressing your
|
||
intention (meta information) via Message headers is natural in a Messaging architecture.
|
||
|
||
<programlisting language="xml"><![CDATA[<int:gateway id="loanBrokerGateway"
|
||
service-interface="org.springframework.integration.loanbroker.LoanBrokerGateway">
|
||
<int:method name="getLoanQuote" request-channel="loanBrokerPreProcessingChannel">
|
||
<int:header name="RESPONSE_TYPE" value="BEST"/>
|
||
</int:method>
|
||
<int:method name="getAllLoanQuotes" request-channel="loanBrokerPreProcessingChannel">
|
||
<int:header name="RESPONSE_TYPE" value="ALL"/>
|
||
</int:method>
|
||
</int:gateway>]]></programlisting>
|
||
In the above case you can clearly see how a different value will be set for the 'RESPONSE_TYPE'
|
||
header based on the gateway's method.
|
||
</para>
|
||
<para>
|
||
Of course, the Gateway invocation might result in errors.
|
||
By default any error that has occurred downstream will be re-thrown as a MessagingException (RuntimeException)
|
||
upon the Gateway's method invocation. However there are times when you may want
|
||
to simply log the error rather than propagating it, or you may want to treat an
|
||
Exception as a valid reply, by mapping it to a Message that will conform to some
|
||
"error message" contract that the caller understands. To accomplish this, our
|
||
Gateway provides support for a Message Channel dedicated to the errors via the
|
||
<emphasis>error-channel</emphasis> attribute. In the example below, you can see
|
||
that a 'transformer' is used to create a reply Message from the Exception.
|
||
</para>
|
||
<para>
|
||
<programlisting language="xml"><![CDATA[<si:gateway id="sampleGateway"
|
||
default-request-channel="gatewayChannel"
|
||
service-interface="foo.bar.SimpleGateway"
|
||
error-channel="exceptionTransformationChannel"/>
|
||
|
||
<si:transformer input-channel="exceptionTransformationChannel"
|
||
ref="exceptionTransformer" method="createErrorResponse"/>
|
||
]]></programlisting>
|
||
|
||
The <emphasis>exceptionTransformer</emphasis> could be a simple POJO that
|
||
knows how to create the expected error response objects. That would then be
|
||
the payload that is sent back to the caller. Obviously, you could do many
|
||
more elaborate things in such an "error flow" if necessary. It might involve
|
||
routers (including Spring Integration's ErrorMessageExceptionTypeRouter),
|
||
filters, and so on. Most of the time, a simple 'transformer' should be sufficient,
|
||
however.
|
||
</para>
|
||
<para>
|
||
Alternatively, you might want to only log the Exception (or send it somewhere
|
||
asynchronously). If you provide a one-way flow, then nothing would be sent
|
||
back to the caller. In the case that you want to completely suppress Exceptions,
|
||
you can provide a reference to the global "nullChannel" (essentially a /dev/null
|
||
approach). Finally, as mentioned above, if no "error-channel" is defined at all,
|
||
then the Exceptions will propagate as usual.
|
||
</para>
|
||
<para>
|
||
<important>
|
||
Exposing the messaging system via simple POJI Gateways obviously provides benefits, but "hiding" the reality
|
||
of the underlying messaging system does come at a price so there are certain things you should consider.
|
||
|
||
We want our Java method to return as quickly as possible and not hang for an indefinite amount of time while
|
||
the caller is waiting on it to return (void, return value, or a thrown Exception). When regular methods are
|
||
used as a proxies in front of the Messaging system, we have to take into account the potentially asynchronous
|
||
nature of the underlying messaging. This means that there might
|
||
be a chance that a Message that was initiated by a Gateway could be dropped by a Filter, thus never reaching a
|
||
component that is responsible for producing a reply. Some Service Activator method might result in an Exception,
|
||
thus providing no reply (as we don't generate Null messages). So as you can see there are multiple scenarios
|
||
where a reply message might not be coming. That is perfectly natural in messaging systems. However think about the
|
||
implication on the gateway method. The Gateway's method input arguments were incorporated into a Message and
|
||
sent downstream. The reply Message would be converted to a return value of the Gateway's method. So you might want
|
||
to ensure that for each Gateway call there will always be a reply Message.
|
||
Otherwise, your Gateway method might never return and will hang indefinitely.
|
||
One of the ways of handling this situation is via an Asynchronous Gateway (explained later in this section). Another way of handling it is to explicitly set the reply-timeout attribute. That way, the gateway will not hang any longer than the time specified by the reply-timeout and will return 'null' if that timeout does elapse. Finally, you might want to consider setting downstream flags such as 'requires-reply' on
|
||
a service-activator or 'throw-exceptions-on-rejection' on a filter. These options will be discussed in more detail in the final section
|
||
of this chapter.
|
||
</important>
|
||
</para>
|
||
</section>
|
||
<section id="async-gateway">
|
||
<title>Asynchronous Gateway</title>
|
||
<para>
|
||
As a pattern the Messaging Gateway is a very nice way to hide messaging-specific code while still exposing the full capabilities of the
|
||
messaging system. As you've seen, the <classname>GatewayProxyFactoryBean</classname> provides a convenient way to expose a Proxy over a service-interface
|
||
thus giving you POJO-based access to a messaging system (based on objects in your own domain, or primitives/Strings, etc). But when a
|
||
gateway is exposed via simple POJO methods which return values it does imply that for each Request message (generated when the method is invoked)
|
||
there must be a Reply message (generated when the method has returned). Since Messaging systems naturally are asynchronous you may not always be
|
||
able to guarantee the contract where <emphasis>"for each request there will always be be a reply"</emphasis>.
|
||
With Spring Integration 2.0 we are introducing support for an <emphasis>Asynchronous Gateway</emphasis> which is a convenient way to initiate
|
||
flows where you may not know if a reply is expected or how long will it take for replies to arrive.
|
||
</para>
|
||
<para>
|
||
A natural way to handle these types of scenarios in Java would be relying upon <emphasis>java.util.concurrent.Future</emphasis> instances, and
|
||
that is exactly what Spring Integration uses to support an <emphasis>Asynchronous Gateway</emphasis>.
|
||
</para>
|
||
<para>
|
||
From the XML configuration, there is nothing different and you still define <emphasis>Asynchronous Gateway</emphasis> the same way as a regular Gateway.
|
||
<programlisting language="xml"><![CDATA[<int:gateway id="mathService"
|
||
service-interface="org.springframework.integration.sample.gateway.futures.MathServiceGateway"
|
||
default-request-channel="requestChannel"/>]]></programlisting>
|
||
However the Gateway Interface (service-interface) is a bit different.
|
||
|
||
<programlisting language="java">public interface MathServiceGateway {
|
||
Future<Integer> multiplyByTwo(int i);
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
As you can see from the example above the return type for the gateway method is a <classname>Future</classname>. When
|
||
<classname>GatewayProxyFactoryBean</classname> sees that the
|
||
return type of the gateway method is a <classname>Future</classname>, it immediately switches to the async mode by utilizing
|
||
an <classname>AsyncTaskExecutor</classname>. That is all. The call to such a method always returns immediately with a <classname>Future</classname> instance.
|
||
Then, you can interact with the <classname>Future</classname> at your own pace to get the result, cancel, etc. And, as with
|
||
any other use of Future instances, calling get() may reveal a timeout, an execution exception, and so on.
|
||
<programlisting language="java">MathServiceGateway mathService = ac.getBean("mathService", MathServiceGateway.class);
|
||
Future<Integer> result = mathService.multiplyByTwo(number);
|
||
// do something else here since the reply might take a moment
|
||
int finalResult = result.get(1000, TimeUnit.SECONDS);</programlisting>
|
||
For a more detailed example, please refer to the <emphasis>async-gateway</emphasis> sample distributed within the Spring Integration samples.
|
||
</para>
|
||
|
||
</section>
|
||
<section>
|
||
<title>Gateway behavior when no response arrives</title>
|
||
<para>
|
||
As it was explained earlier, the Gateway provides a convenient way of interacting with a Messaging system via POJO method
|
||
invocations, but realizing that a typical method invocation, which is generally expected to always return (even with an Exception),
|
||
might not always map one-to-one to message exchanges (e.g., a reply message might not arrive - which is equivalent to a
|
||
method not returning). It is important to go over several scenarios especially in the Sync Gateway case and understand
|
||
the default behavior of the Gateway and how to deal with these scenarios to make the Sync Gateway behavior more
|
||
predictable regardless of the outcome of the message flow that was initialed from such Gateway.
|
||
</para>
|
||
<para>
|
||
There are certain attributes that could be configured to make Sync Gateway behavior more predictable,
|
||
but some of them might not always work as you might have expected. One of them is <emphasis>reply-timeout</emphasis>.
|
||
So, lets look at the <emphasis>reply-timeout</emphasis> attribute and see how it can/can't influence the behavior
|
||
of the Sync Gateway in various scenarios. We will look at single-threaded scenario
|
||
(all components downstream are connected via Direct Channel) and multi-threaded scenarios
|
||
(e.g., somewhere downstream you may have Pollable or Executor Channel which breaks single-thread boundary)
|
||
</para>
|
||
<para>
|
||
<emphasis>Long running process downstream</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream is still running (e.g., infinite loop or a very slow service), then setting a <emphasis>reply-timeout</emphasis>
|
||
has no effect and the Gateway method call will not return until such downstream service exits (via return or exception).
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis>.
|
||
If a component downstream is still running (e.g., infinite loop or a very slow service), in a multi-threaded message
|
||
flow setting the <emphasis>reply-timeout</emphasis> will have an effect by allowing gateway method invocation to
|
||
return once the timeout has been reached, since the <classname>GatewayProxyFactoryBean</classname> will simply
|
||
poll on the reply channel waiting for a message until the timeout expires. However it could result in a 'null' return
|
||
from the Gateway method if the timeout has been reached before the actual reply was produced. It is also important to understand that
|
||
the reply message (if produced) will be sent to a reply channel after the Gateway method invocation might have returned, so you must be aware of that and design your flow with this in mind.
|
||
</para>
|
||
<para>
|
||
<emphasis>Downstream component returns 'null'</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream returns 'null' and no <emphasis>reply-timeout</emphasis> has been configured, the Gateway
|
||
method call will hang indefinitely unless: a) a <emphasis>reply-timeout</emphasis> has been configured or b) the
|
||
<emphasis>requires-reply</emphasis> attribute has been set on the downstream component (e.g., service-activator)
|
||
that might return 'null'. In this case, an Exception would be thrown and propagated to the Gateway.
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis>. Behavior is the same as above.
|
||
</para>
|
||
<para>
|
||
<emphasis>Downstream component return signature is 'void' while Gateway method signature is non-void</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream returns 'void' and no <emphasis>reply-timeout</emphasis> has been configured,
|
||
the Gateway method call will hang indefinitely unless a <emphasis>reply-timeout</emphasis> has been configured
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis> Behavior is the same as above.
|
||
</para>
|
||
<para>
|
||
<emphasis>Downstream component results in Runtime Exception (regardless of the method signature)</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Sync Gateway - single-threaded</emphasis>.
|
||
If a component downstream throws a Runtime Exception, such exception will be propagated via an Error Message back to
|
||
the gateway and re-thrown.
|
||
<emphasis>Sync Gateway - multi-threaded</emphasis> Behavior is the same as above.
|
||
</para>
|
||
<para>
|
||
<important>
|
||
It is also important to understand that by default <emphasis>reply-timeout</emphasis> is unbounded which means that
|
||
if not explicitly set there are several scenarios (described above) where your Gateway method invocation might
|
||
hang indefinitely. So, make sure you analyze your flow and if there is even a remote possibility of one of these
|
||
scenarios to occur, set the <emphasis>reply-timeout</emphasis> attribute to a 'safe' value or, even better,
|
||
set the <emphasis>requires-reply</emphasis> attribute of the downstream component to 'true' to ensure a timely response
|
||
as produced by the throwing of an Exception as soon as that downstream component does return null internally.
|
||
But also, realize that there are some scenarios (see the very first one)
|
||
where <emphasis>reply-timeout</emphasis> will not help. That means it is also important to analyze your message
|
||
flow and decide when to use a Sync Gateway vs an Async Gateway. As you've seen the latter case is simply a matter of
|
||
defining Gateway methods that return Future instances. Then, you are guaranteed to receive that return value, and
|
||
you will have more granular control over the results of the invocation.
|
||
<para>
|
||
Also, when dealing with a Router you should remember that setting the <emphasis>resolution-required</emphasis> attribute to 'true'
|
||
will result in an Exception thrown by the router if it can not resolve a particular channel. Likewise, when dealing with a Filter,
|
||
you can set the <emphasis>throw-exception-on-rejection</emphasis> attribute. In both of these cases, the resulting flow will
|
||
behave like that containing a service-activator with the 'requires-reply' attribute. In other words, it will help to ensure
|
||
a timely response from the Gateway method invocation.
|
||
</para>
|
||
</important>
|
||
</para>
|
||
</section>
|
||
|
||
</section>
|