654 lines
39 KiB
XML
654 lines
39 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<appendix xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="samples"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Spring Integration Samples</title>
|
||
|
||
<section id="samples-introduction">
|
||
<title>Introduction</title>
|
||
<para>
|
||
As of Spring Integration 2.0, the <emphasis>samples</emphasis> are no longer included with the
|
||
Spring Integration distribution. Instead we have switched to a much simpler collaborative model that should promote
|
||
better community participation and, ideally, more contributions. Samples now have a dedicated Git repository and a
|
||
dedicated JIRA Issue Tracking system. Sample development will also have its own lifecycle which is not dependent on the
|
||
lifecycle of the framework releases although the repository will still be tagged with each major release for compatibility
|
||
reasons.
|
||
</para>
|
||
<para>
|
||
The great benefit to the community is that we can now add more samples and make them available to you right away
|
||
without waiting for the next release. Having its own JIRA that is not tied to the the actual
|
||
framework is also a great benefit. You now have a dedicated place to suggest samples as well as report issues with existing
|
||
samples. Or, <emphasis> you may want to submit a sample to us</emphasis> as an attachment through the JIRA or, better, through
|
||
the collaborative model that Git promotes. If we believe your sample adds value, we
|
||
would be more then glad to add it to the 'samples' repository, properly crediting you as the author.
|
||
</para>
|
||
</section>
|
||
|
||
<section id="samples-get">
|
||
<title>Where to get Samples</title>
|
||
<para>
|
||
To monitor samples development and to get more information on the repository you can visit the following
|
||
URL: <link linkend="http://git.springsource.org/spring-integration/samples">http://git.springsource.org/spring-integration/samples</link>
|
||
Since we are using Git as the SCM, we should use the proper terminology as well when it comes to the tasks you need to perform to make
|
||
<emphasis>samples</emphasis> available locally on your machine. For more information on Git SCM please visit their
|
||
website: <link linkend="http://git-scm.com/">http://git-scm.com/</link>
|
||
</para>
|
||
<para>
|
||
CLONE <emphasis>samples</emphasis> repository. (For those unfamiliar with Git, this is somewhat the equivalent of a checkout.)
|
||
</para>
|
||
<para>
|
||
This is the first step you should go through. You must have Git installed on your machine. There are many GUI-based products
|
||
available for many platforms. A simple Google search will help you find them.
|
||
To clone the Spring Integration samples repository, issue the following at the command line:
|
||
<programlisting language="xml"><![CDATA[> mkdir spring-integration-samples
|
||
> cd spring-integration-samples
|
||
> git clone git://git.springsource.org/spring-integration/samples.git]]></programlisting>
|
||
</para>
|
||
<para>
|
||
That is all you need to do. Now you have cloned the entire samples repository. Since the samples repository is a live
|
||
repository, you might want to perform periodic "pulls" to get new samples as well as updates to the existing samples.
|
||
To get the updates use the git PULL command:
|
||
<programlisting language="xml"><![CDATA[> git pull]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Submit samples or sample requests
|
||
</para>
|
||
<para>
|
||
As mentioned earlier, Spring Integration <emphasis>samples</emphasis> have a dedicated JIRA Issue tracking system.
|
||
To submit new sample requests or to submit an actual sample (as an attachment), please visit our JIRA Issue Tracking system:
|
||
<link linkend="https://jira.springframework.org/browse/INTSAMPLES">https://jira.springsource.org/browse/INTSAMPLES</link>
|
||
</para>
|
||
</section>
|
||
<section id="samples-structure">
|
||
<title>Samples Structure</title>
|
||
<para>
|
||
The structure of the <emphasis>samples</emphasis> changed as well. With plans for more samples we realized that some
|
||
samples have different goals than others. While they all share the common goal of showing you how to apply and work with
|
||
the Spring Integration framework, they also differ in areas where some samples are meant to concentrate on a technical
|
||
use case while others focus on a business use case, and some samples are all about showcasing various techniques that
|
||
could be applied to address certain scenarios (both technical and business). The new categorization of samples will allow us
|
||
to better organize them based on the problem each sample addresses while giving you a simpler way of finding the right sample
|
||
for your needs.
|
||
</para>
|
||
<para>
|
||
Currently there are 4 categories. Within the samples repository each category has its own directory which is named after the
|
||
category name:
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>BASIC (samples/basic)</emphasis>
|
||
</para>
|
||
<para>
|
||
This is a good place to get started. The samples here are technically motivated and demonstrate the bare
|
||
minimum with regard to configuration and code. These should help you to get started quickly by introducing you to the basic concepts,
|
||
API and configuration of Spring Integration as well as Enterprise Integration Patterns (EIP). For example, if you are
|
||
looking for an answer on how to implement and wire a <emphasis>Service Activator</emphasis> to a <emphasis>Message Channel</emphasis>
|
||
or how to use a <emphasis>Messaging Gateway</emphasis> as a facade to your message exchange, or how to get started with using MAIL or
|
||
TCP/UDP modules etc., this would be the right place to find a good sample. The bottom line is this is a good place
|
||
to get started.
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>INTERMEDIATE (samples/intermediate)</emphasis>
|
||
</para>
|
||
<para>
|
||
This category targets developers who are already familiar with the Spring Integration framework (past getting started),
|
||
but need some more guidance while resolving the more advanced technical problems one might deal with
|
||
after switching to a Messaging architecture.
|
||
For example, if you are looking for an answer on how to handle errors in various message exchange
|
||
scenarios or how to properly configure the <emphasis>Aggregator</emphasis> for the situations where some messages
|
||
might not ever arrive for aggregation, or any other issue that goes beyond a basic implementation and configuration
|
||
of a particular component and addresses <emphasis>what else</emphasis> types of problems, this
|
||
would be the right place to find these type of samples.
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>ADVANCED (samples/advanced)</emphasis>
|
||
</para>
|
||
<para>
|
||
This category targets developers who are very familiar with the Spring Integration framework but are looking to
|
||
extend it to address a specific custom need by using Spring Integration's public API.
|
||
For example, if you are looking for samples showing you how to implement a custom <emphasis>Channel</emphasis> or
|
||
<emphasis>Consumer</emphasis> (event-based or polling-based), or you are trying to figure out what is the most appropriate
|
||
way to implement a custom Bean parser on top of the Spring Integration Bean parser hierarchy when implementing your own
|
||
namespace and schema for a custom component, this would be the right place to look.
|
||
Here you can also find samples that will help you with <emphasis>Adapter</emphasis> development. Spring Integration comes
|
||
with an extensive library of adapters to allow you to connect remote systems with the Spring Integration messaging framework.
|
||
However you might have a need to integrate with a system for which the core framework does not provide an adapter.
|
||
So, you may decide to implement your own (and potentially contribute it). This category would include samples showing you how.
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>APPLICATIONS (samples/applications)</emphasis>
|
||
</para>
|
||
<para>
|
||
This category targets developers and architects who have a good understanding of Message-driven architecture and
|
||
EIP, and an above average understanding of Spring and Spring Integration who are looking for samples that
|
||
address a particular <emphasis>business problem</emphasis>. In other words the emphasis of samples in this category
|
||
is <emphasis>business use cases</emphasis> and how they can be solved with a Message-Driven Architecture and Spring Integration
|
||
in particular.
|
||
For example, if you are interested to see how a <emphasis>Loan Broker</emphasis> or <emphasis>Travel Agent</emphasis>
|
||
process could be implemented and automated via Spring Integration, this would be the right place to find these types of samples.
|
||
</para>
|
||
|
||
<important>
|
||
<remark>
|
||
Remember: Spring Integration is a community driven framework, therefore community participation is IMPORTANT.
|
||
That includes Samples; so, if you can't find what you are looking for, let us know!
|
||
</remark>
|
||
</important>
|
||
</section>
|
||
<section id="samples-impl">
|
||
<title>Samples</title>
|
||
<para>
|
||
Currently Spring Integration comes with quite a few samples and you can only expect more.
|
||
To help you better navigate through them, each sample comes with its own <code>readme.txt</code> file which covers
|
||
several details about the sample (e.g., what EIP patterns it addresses, what problem it is trying to solve, how to run sample etc.).
|
||
However, certain samples require a more detailed and sometimes graphical explanation. In this section you'll
|
||
find details on samples that we believe require special attention.
|
||
</para>
|
||
<section id="samples-loan-broker">
|
||
<title>Loan Broker</title>
|
||
<para>
|
||
In this section, we will review the <emphasis>Loan Broker</emphasis> sample application that is included in the
|
||
Spring Integration samples. This sample is inspired by one of the samples featured in Gregor
|
||
Hohpe and Bobby Woolf's book, <ulink url="http://www.eaipatterns.com">Enterprise Integration Patterns</ulink>.
|
||
</para>
|
||
<para>The diagram below represents the entire process</para>
|
||
<para>
|
||
<mediaobject>
|
||
<imageobject role="fo">
|
||
<imagedata fileref="images/loan-broker-eip.png" format="PNG" align="center" scalefit="1" width="100%" contentdepth="100%" />
|
||
</imageobject>
|
||
<imageobject role="html">
|
||
<imagedata fileref="images/loan-broker-eip.png" format="PNG" align="center"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
<para>Now lets look at this process in more detail</para>
|
||
<para>
|
||
At the core of an EIP architecture are the very simple yet powerful concepts of Pipes and Filters, and of course: Messages.
|
||
Endpoints (Filters) are connected with one another via Channels (Pipes). The producing endpoint sends Message to the Channel,
|
||
and the Message is retrieved
|
||
by the Consuming endpoint. This architecture is meant to define various mechanisms that describe HOW information is exchanged between
|
||
the endpoints, without any awareness of WHAT those endpoints are or what information they are exchanging. Thus, it provides for a very loosely
|
||
coupled and flexible collaboration model while also decoupling Integration concerns from Business concerns. EIP extends this architecture
|
||
by further defining:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>The types of pipes (Point-to-Point Channel, Publish-Subscribe Channel, Channel Adapter, etc.)</para>
|
||
</listitem>
|
||
|
||
<listitem>
|
||
<para>The core filters and patterns around how filters collaborate with pipes
|
||
(Message Router, Splitters and Aggregators, various Message Transformation patterns, etc.)</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
<para>
|
||
The details and variations of this use case are very nicely described in Chapter 9 of the EIP Book, but here is the brief summary;
|
||
A Consumer while shopping for the best Loan Quote(s) subscribes to the services of a Loan Broker, which handles details such as:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>Consumer pre-screening (e.g., obtain and review the consumer's Credit history)</para>
|
||
</listitem>
|
||
|
||
<listitem>
|
||
<para>Determine the most appropriate Banks (e.g., based on consumer's credit history/score)</para>
|
||
</listitem>
|
||
|
||
<listitem>
|
||
<para>Send a Loan quote request to each selected Bank</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>Collect responses from each Bank</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>Filter responses and determine the best quote(s), based on consumer's requirements.</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>Pass the Loan quote(s) back to the consumer.</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
<para>
|
||
Obviously the real process of obtaining a loan quote is a bit more complex, but since our goal here is to demonstrate how
|
||
Enterprise Integration Patterns are realized and implemented within SI, the use case has been simplified to concentrate only on
|
||
the Integration aspects of the process. It is not an attempt to give you an advice in consumer finances.
|
||
</para>
|
||
<para>
|
||
As you can see, by hiring a Loan Broker, the consumer is isolated from the details of the Loan Broker's operations, and each Loan Broker's
|
||
operations may defer from one another to maintain competitive advantage, so whatever we assemble/implement must be flexible so any changes
|
||
could be introduced quickly and painlessly.
|
||
Speaking of change, the Loan Broker sample does not actually talk to any 'imaginary' Banks or Credit bureaus. Those services are stubbed out.
|
||
Our goal here is to assemble, orchestrate and test the integration aspect of the process as a whole. Only then can we start thinking about
|
||
wiring such process to the real services. At that time the assembled process and its configuration will not change regardless of the number
|
||
of Banks a particular Loan Broker is dealing with, or the type of communication media (or protocols) used (JMS, WS, TCP, etc.)
|
||
to communicate with these Banks.
|
||
</para>
|
||
<para> <emphasis>DESIGN</emphasis> </para>
|
||
<para>
|
||
As you analyze the 6 requirements above you'll quickly see that they all fall into the category of Integration concerns.
|
||
For example, in the consumer pre-screening step we need to gather additional information about the consumer and the consumer's desires
|
||
and enrich the loan request with additional meta information. We then have to filter such information to select the most appropriate list of
|
||
Banks, and so on. Enrich, filter, select – these are all integration concerns for which EIP defines a solution in the form of patterns.
|
||
SI provides an implementation of these patterns.
|
||
</para>
|
||
<para><emphasis>Messaging Gateway</emphasis>
|
||
<mediaobject>
|
||
<imageobject role="fo">
|
||
<imagedata fileref="images/gateway.jpg" format="JPG" align="center"/>
|
||
</imageobject>
|
||
<imageobject role="html">
|
||
<imagedata fileref="images/gateway.jpg" format="JPG" align="center"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
<para>
|
||
The <emphasis>Messaging Gateway</emphasis> pattern provides a simple mechanism to access messaging systems, including our Loan Broker.
|
||
In SI you define the <emphasis>Gateway</emphasis> as a Plain Old Java Interface (no need to provide an implementation), configure it via the
|
||
XML <emphasis><gateway></emphasis> element or via annotation and use it as any other Spring bean. SI will take care of
|
||
delegating and mapping method invocations to the Messaging infrastructure by generating a <emphasis>Message</emphasis> (payload is mapped to an
|
||
input parameter of the method) and sending it to the designated channel.
|
||
<programlisting language="xml"><![CDATA[<gateway id="loanBrokerGateway"
|
||
default-request-channel="loanBrokerPreProcessingChannel"
|
||
service-interface="org.springframework.integration.samples.loanbroker.LoanBrokerGateway">
|
||
<method name="getBestLoanQuote">
|
||
<header name="RESPONSE_TYPE" value="BEST"/>
|
||
</method>
|
||
</gateway>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Our current <emphasis>Gateway</emphasis> provides two methods that could be invoked. One that will return the best single quote and another one that
|
||
will return all quotes. Somehow downstream we need to know what type of reply the caller is looking for. The best way to achieve
|
||
this in Messaging architecture is to enrich the content of the message with some meta-data describing your intentions.
|
||
<emphasis>Content Enricher</emphasis> is one of the patterns that addresses this and although Spring Integration does provide a
|
||
separate configuration element to enrich Message Headers with arbitrary data (we'll see it later), as a convenience, since
|
||
<emphasis>Gateway</emphasis> element is responsible to construct the initial <emphasis>Message</emphasis> it provides embedded
|
||
capability to enrich the newly created <emphasis>Message</emphasis> with arbitrary <emphasis>Message Headers</emphasis>. In our
|
||
example we are adding header RESPONSE_TYPE with value 'BEST'' whenever the getBestQuote() method is invoked. For other method
|
||
we are not adding any header. Now we can check downstream for an existence of this header and based on its presence and its value
|
||
we can determine what type of reply the caller is looking for.
|
||
</para>
|
||
|
||
<para>
|
||
Based on the use case we also know there are some pre-screening steps that needs to be performed such as getting and evaluating the consumer's
|
||
credit score, simply because some premiere Banks will only typically accept quote requests from consumers that meet a minimum credit
|
||
score requirement. So it would be nice if the <emphasis>Message</emphasis> would be enriched with such information before it is forwarded
|
||
to the Banks. It would also be nice if when several processes needs to be completed to provide such meta-information, those
|
||
processes could be grouped in a single unit. In our use case we need to determine credit score and based on the credit score and some
|
||
rule select a list of <emphasis>Message Channels</emphasis> (Bank Channels) we will sent quote request to.
|
||
</para>
|
||
<para><emphasis>Composed Message Processor</emphasis> </para>
|
||
<para>
|
||
The <emphasis>Composed Message Processor</emphasis> pattern describes rules around building endpoints that maintain control over message flow which
|
||
consists of multiple message processors. In Sprig Integration <emphasis>Composed Message Processor</emphasis> pattern is implemented via
|
||
<emphasis><chain></emphasis> element.
|
||
<mediaobject>
|
||
<imageobject role="fo">
|
||
<imagedata fileref="images/chain.png" format="PNG" align="center"/>
|
||
</imageobject>
|
||
<imageobject role="html">
|
||
<imagedata fileref="images/chain.png" format="PNG" align="center"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
<para>
|
||
As you can see from the above configuration we have a chain with inner header-enricher element which will further enrich the
|
||
content of the <emphasis>Message</emphasis> with the header CREDIT_SCORE and value that will be determined by the call to a
|
||
credit service (simple POJO spring bean identified by 'creditBureau' name) and then it will delegate to the <emphasis>Message Router</emphasis>
|
||
</para>
|
||
<para><emphasis>Message Router</emphasis>
|
||
<mediaobject>
|
||
<imageobject role="fo">
|
||
<imagedata fileref="images/bank-router.jpg" format="JPG" align="center"/>
|
||
</imageobject>
|
||
<imageobject role="html">
|
||
<imagedata fileref="images/bank-router.jpg" format="JPG" align="center"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
<para>
|
||
There are several implementations of the <emphasis>Message Routing</emphasis> pattern available in Spring Integration. Here we are using a
|
||
router that will determine a list of channels based on evaluating an expression (Spring Expression Language) which will look at
|
||
the credit score that was determined is the previous step and will select the list of channels from the Map bean with id 'banks'
|
||
whose values are 'premier' or 'secondary' based o the value of credit score. Once the list of <emphasis>Channels</emphasis> is selected, the
|
||
<emphasis>Message</emphasis> will be routed to those <emphasis>Channels</emphasis>.
|
||
</para>
|
||
<para>
|
||
Now, one last thing the Loan Broker needs to to is to receive the loan quotes form the banks, aggregate them by consumer
|
||
(we don't want to show quotes from one consumer to another), assemble the response based on the consumer's selection criteria
|
||
(single best quote or all quotes) and reply back to the consumer.
|
||
</para>
|
||
<para><emphasis>Message Aggregator</emphasis>
|
||
<mediaobject>
|
||
<imageobject role="fo">
|
||
<imagedata fileref="images/quotes-aggregator.jpg" format="JPG" align="center"/>
|
||
</imageobject>
|
||
<imageobject role="html">
|
||
<imagedata fileref="images/quotes-aggregator.jpg" format="JPG" align="center"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
|
||
<para>
|
||
An <emphasis>Aggregator</emphasis> pattern describes an endpoint which groups related <emphasis>Messages</emphasis> into a single
|
||
<emphasis>Message</emphasis>. Criteria and rules can be provided to determine an aggregation and correlation strategy.
|
||
SI provides several implementations of the <emphasis>Aggregator</emphasis> pattern as well as a convenient name-space based configuration.
|
||
<programlisting language="xml"><![CDATA[<aggregator id="quotesAggregator"
|
||
input-channel="quotesAggregationChannel"
|
||
method="aggregateQuotes">
|
||
<beans:bean class="org.springframework.integration.samples.loanbroker.LoanQuoteAggregator"/>
|
||
</aggregator>]]></programlisting>
|
||
</para>
|
||
|
||
<para>
|
||
Our Loan Broker defines a 'quotesAggregator' bean via the <emphasis><aggregator></emphasis> element which provides a default
|
||
aggregation and correlation strategy. The default correlation strategy correlates messages based on the <code>correlationId</code> header
|
||
(see <emphasis>Correlation Identifier</emphasis> pattern). What's interesting is that we never provided the value for this header.
|
||
It was set earlier by the router automatically, when it generated a separate <emphasis>Message</emphasis> for each Bank channel.
|
||
</para>
|
||
<para>
|
||
Once the <emphasis>Messages</emphasis> are correlated they are released to the actual <emphasis>Aggregator</emphasis> implementation.
|
||
Although default <emphasis>Aggregator</emphasis> is provided by SI, its strategy (gather the list of payloads from all
|
||
<emphasis>Messages</emphasis> and construct a new <emphasis>Message</emphasis> with this List as payload) does not satisfy our
|
||
requirement. The reason is that our consumer might require a single best quote or all quotes. To communicate the consumer's
|
||
intention, earlier in the process we set the RESPONSE_TYPE header. Now we have to evaluate this header and return either
|
||
all the quotes (the default aggregation strategy would work) or the best quote (the default aggregation strategy will not work
|
||
because we have to determine which loan quote is the best).
|
||
</para>
|
||
|
||
<para>
|
||
Obviously selecting the best quote could be based on complex criteria and would influence the complexity of the aggregator implementation and
|
||
configuration, but for now we are making it simple. If consumer wants the best quote we will select a quote with the lowest interest
|
||
rate. To accomplish that the LoanQuoteAggregator.java will sort all the quotes and return the first one.
|
||
The <classname>LoanQuote.java</classname> implements <interfacename>Comparable</interfacename> which compares quotes based on the rate attribute.
|
||
Once the response <emphasis>Message</emphasis> is created it is sent to the default-reply-channel of the <emphasis>Messaging Gateway</emphasis>
|
||
(thus the consumer) which started the process. Our consumer got the Loan Quote!
|
||
</para>
|
||
<para>Conclusion</para>
|
||
<para>
|
||
As you can see a rather complex process was assembled based on POJO (read existing, legacy), light weight, embeddable messaging
|
||
framework (Spring Integration) with a loosely coupled programming model intended to simplify integration of heterogeneous systems
|
||
without requiring a heavy-weight ESB-like engine or proprietary development and deployment environment, because as a developer you
|
||
should not be porting your Swing or console-based application to an ESB-like server or implementing proprietary interfaces just
|
||
because you have an integration concern.
|
||
</para>
|
||
<para>
|
||
This and other samples in this section are build on top of Enterprise Integration Patterns that meant to describe "building blocks"
|
||
for YOUR solution but not to be solutions in of themselves. Integration concerns exist in all types of applications (server based and not)
|
||
and should not require change in design, testing and deployment strategy if such applications need to integrate with one another.
|
||
</para>
|
||
</section>
|
||
|
||
<section id="samples-cafe">
|
||
<title>The Cafe Sample</title>
|
||
<para>
|
||
In this section, we will review a <emphasis>Cafe</emphasis> sample application that is included in the
|
||
Spring Integration samples. This sample is inspired by another sample featured in Gregor
|
||
Hohpe's <ulink url="http://www.eaipatterns.com/ramblings.html">Ramblings</ulink>.
|
||
</para>
|
||
<para>
|
||
The domain is that of a Cafe, and the basic flow is depicted in the following diagram:
|
||
</para>
|
||
<para>
|
||
<mediaobject>
|
||
<imageobject role="fo">
|
||
<imagedata fileref="images/cafe-eip.png" format="PNG" align="center" scalefit="1" width="100%" contentdepth="100%" />
|
||
</imageobject>
|
||
<imageobject role="html">
|
||
<imagedata fileref="images/cafe-eip.png" format="PNG" align="center"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
<para>
|
||
The <classname>Order</classname> object may contain multiple <classname>OrderItems</classname>. Once the order
|
||
is placed, a <emphasis>Splitter</emphasis> will break the composite order message into a single message per
|
||
drink. Each of these is then processed by a <emphasis>Router</emphasis> that determines whether the drink is hot
|
||
or cold (checking the <classname>OrderItem</classname> object's 'isIced' property). The
|
||
<classname>Barista</classname> prepares each drink, but hot and cold drink preparation are handled by two
|
||
distinct methods: 'prepareHotDrink' and 'prepareColdDrink'. The prepared drinks are then sent to the Waiter where
|
||
they are aggregated into a <classname>Delivery</classname> object.
|
||
</para>
|
||
<para>
|
||
Here is the XML configuration:
|
||
<programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
|
||
<beans:beans xmlns="http://www.springframework.org/schema/integration"
|
||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||
xmlns:stream="http://www.springframework.org/schema/integration/stream"
|
||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||
http://www.springframework.org/schema/integration
|
||
http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
|
||
http://www.springframework.org/schema/integration/stream
|
||
http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.0.xsd">
|
||
|
||
<gateway id="cafe" service-interface="org.springframework.integration.samples.cafe.Cafe"/>
|
||
|
||
<channel id="orders"/>
|
||
<splitter input-channel="orders" ref="orderSplitter" method="split" output-channel="drinks"/>
|
||
|
||
<channel id="drinks"/>
|
||
<router input-channel="drinks" ref="drinkRouter" method="resolveOrderItemChannel"/>
|
||
|
||
<channel id="coldDrinks">
|
||
<queue capacity="10"/>
|
||
</channel>
|
||
<service-activator input-channel="coldDrinks" ref="barista"
|
||
method="prepareColdDrink" output-channel="preparedDrinks"/>
|
||
|
||
<channel id="hotDrinks">
|
||
<queue capacity="10"/>
|
||
</channel>
|
||
<service-activator input-channel="hotDrinks" ref="barista"
|
||
method="prepareHotDrink" output-channel="preparedDrinks"/>
|
||
|
||
<channel id="preparedDrinks"/>
|
||
<aggregator input-channel="preparedDrinks" ref="waiter"
|
||
method="prepareDelivery" output-channel="deliveries"/>
|
||
|
||
<stream:stdout-channel-adapter id="deliveries"/>
|
||
|
||
<beans:bean id="orderSplitter"
|
||
class="org.springframework.integration.samples.cafe.xml.OrderSplitter"/>
|
||
|
||
<beans:bean id="drinkRouter"
|
||
class="org.springframework.integration.samples.cafe.xml.DrinkRouter"/>
|
||
|
||
<beans:bean id="barista" class="org.springframework.integration.samples.cafe.xml.Barista"/>
|
||
|
||
<beans:bean id="waiter" class="org.springframework.integration.samples.cafe.xml.Waiter"/>
|
||
|
||
<poller id="poller" default="true" fixed-rate="1000"/>
|
||
|
||
</beans:beans>]]></programlisting>
|
||
As you can see, each Message Endpoint is connected to input and/or output channels. Each endpoint will manage
|
||
its own Lifecycle (by default endpoints start automatically upon initialization - to prevent that add the
|
||
"auto-startup" attribute with a value of "false"). Most importantly, notice that the objects are simple POJOs
|
||
with strongly typed method arguments. For example, here is the Splitter:
|
||
<programlisting language="java"><![CDATA[public class OrderSplitter {
|
||
|
||
public List<OrderItem> split(Order order) {
|
||
return order.getItems();
|
||
}
|
||
}]]></programlisting>
|
||
In the case of the Router, the return value does not have to be a <interfacename>MessageChannel</interfacename>
|
||
instance (although it can be). As you see in this example, a String-value representing the channel name is
|
||
returned instead.
|
||
<programlisting language="java"><![CDATA[public class DrinkRouter {
|
||
|
||
public String resolveOrderItemChannel(OrderItem orderItem) {
|
||
return (orderItem.isIced()) ? "coldDrinks" : "hotDrinks";
|
||
}
|
||
}]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Now turning back to the XML, you see that there are two <service-activator> elements. Each of these
|
||
is delegating to the same <classname>Barista</classname> instance but different methods: 'prepareHotDrink'
|
||
or 'prepareColdDrink' corresponding to the two channels where order items have been routed.
|
||
<programlisting language="java"><![CDATA[public class Barista {
|
||
|
||
private long hotDrinkDelay = 5000;
|
||
private long coldDrinkDelay = 1000;
|
||
|
||
private AtomicInteger hotDrinkCounter = new AtomicInteger();
|
||
private AtomicInteger coldDrinkCounter = new AtomicInteger();
|
||
|
||
public void setHotDrinkDelay(long hotDrinkDelay) {
|
||
this.hotDrinkDelay = hotDrinkDelay;
|
||
}
|
||
|
||
public void setColdDrinkDelay(long coldDrinkDelay) {
|
||
this.coldDrinkDelay = coldDrinkDelay;
|
||
}
|
||
|
||
public Drink prepareHotDrink(OrderItem orderItem) {
|
||
try {
|
||
Thread.sleep(this.hotDrinkDelay);
|
||
System.out.println(Thread.currentThread().getName()
|
||
+ " prepared hot drink #" + hotDrinkCounter.incrementAndGet()
|
||
+ " for order #" + orderItem.getOrder().getNumber() + ": " + orderItem);
|
||
return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
|
||
orderItem.isIced(), orderItem.getShots());
|
||
}
|
||
catch (InterruptedException e) {
|
||
Thread.currentThread().interrupt();
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public Drink prepareColdDrink(OrderItem orderItem) {
|
||
try {
|
||
Thread.sleep(this.coldDrinkDelay);
|
||
System.out.println(Thread.currentThread().getName()
|
||
+ " prepared cold drink #" + coldDrinkCounter.incrementAndGet()
|
||
+ " for order #" + orderItem.getOrder().getNumber() + ": " + orderItem);
|
||
return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
|
||
orderItem.isIced(), orderItem.getShots());
|
||
}
|
||
catch (InterruptedException e) {
|
||
Thread.currentThread().interrupt();
|
||
return null;
|
||
}
|
||
}
|
||
|
||
}]]></programlisting>
|
||
</para>
|
||
<para>
|
||
As you can see from the code excerpt above, the barista methods have different delays (the hot drinks take 5
|
||
times as long to prepare). This simulates work being completed at different rates. When the
|
||
<classname>CafeDemo</classname> 'main' method runs, it will loop 100 times sending a single hot drink and a
|
||
single cold drink each time. It actually sends the messages by invoking the 'placeOrder' method on the Cafe
|
||
interface. Above, you will see that the <gateway> element is specified in the configuration file. This
|
||
triggers the creation of a proxy that implements the given 'service-interface' and connects it to a channel.
|
||
The channel name is provided on the @Gateway annotation of the <interfacename>Cafe</interfacename> interface.
|
||
<programlisting language="java">public interface Cafe {
|
||
|
||
@Gateway(requestChannel="orders")
|
||
void placeOrder(Order order);
|
||
|
||
}</programlisting>
|
||
Finally, have a look at the <methodname>main()</methodname> method of the <classname>CafeDemo</classname> itself.
|
||
<programlisting language="java"><![CDATA[public static void main(String[] args) {
|
||
AbstractApplicationContext context = null;
|
||
if (args.length > 0) {
|
||
context = new FileSystemXmlApplicationContext(args);
|
||
}
|
||
else {
|
||
context = new ClassPathXmlApplicationContext("cafeDemo.xml", CafeDemo.class);
|
||
}
|
||
Cafe cafe = context.getBean("cafe", Cafe.class);
|
||
for (int i = 1; i <= 100; i++) {
|
||
Order order = new Order(i);
|
||
order.addItem(DrinkType.LATTE, 2, false);
|
||
order.addItem(DrinkType.MOCHA, 3, true);
|
||
cafe.placeOrder(order);
|
||
}
|
||
}]]></programlisting>
|
||
</para>
|
||
<tip>
|
||
To run this sample as well as 8 others, refer to the <code>README.txt</code> within the "samples" directory
|
||
of the main distribution as described at the beginning of this chapter.
|
||
</tip>
|
||
<para>
|
||
When you run cafeDemo, you will see that the cold drinks are initially prepared more quickly than the hot drinks.
|
||
Because there is an aggregator, the cold drinks are effectively limited by the rate of the hot drink preparation.
|
||
This is to be expected based on their respective delays of 1000 and 5000 milliseconds. However, by configuring a
|
||
poller with a concurrent task executor, you can dramatically change the results. For example, you could use a
|
||
thread pool executor with 5 workers for the hot drink barista while keeping the cold drink barista as it is:
|
||
<programlisting language="xml"><![CDATA[<service-activator input-channel="hotDrinks"
|
||
ref="barista"
|
||
method="prepareHotDrink"
|
||
output-channel="preparedDrinks"/>
|
||
|
||
<service-activator input-channel="hotDrinks"
|
||
ref="barista"
|
||
method="prepareHotDrink"
|
||
output-channel="preparedDrinks">
|
||
]]><emphasis><![CDATA[<poller task-executor="pool" fixed-rate="1000"/>
|
||
]]></emphasis><![CDATA[
|
||
</service-activator>
|
||
|
||
]]><emphasis><![CDATA[<task:executor id="pool" pool-size="5"/>]]></emphasis></programlisting>
|
||
</para>
|
||
<para>
|
||
Also, notice that the worker thread name is displayed with each invocation. You will see that the hot drinks are
|
||
prepared by the task-executor threads. If you provide a much shorter poller interval (such as 100 milliseconds),
|
||
then you will notice that occasionally it throttles the input by forcing the task-scheduler (the caller) to invoke
|
||
the operation.
|
||
</para>
|
||
<note>
|
||
In addition to experimenting with the poller's concurrency settings, you can also add the 'transactional'
|
||
sub-element and then refer to any PlatformTransactionManager instance within the context.
|
||
</note>
|
||
</section>
|
||
|
||
<section id="samples-xml-messaging">
|
||
<title>The XML Messaging Sample</title>
|
||
<para>
|
||
The xml messaging sample in the <package>org.springframework.integration.samples.xml</package> illustrates how to use
|
||
some of the provided components which deal with xml payloads. The sample uses the idea of processing an order for books
|
||
represented as xml.
|
||
</para>
|
||
<para>
|
||
First the order is split into a number of messages, each one representing a single order item using
|
||
the XPath splitter component.
|
||
<programlisting language="xml"><![CDATA[<si-xml:xpath-splitter id="orderItemSplitter" input-channel="ordersChannel"
|
||
output-channel="stockCheckerChannel" create-documents="true">
|
||
<si-xml:xpath-expression expression="/orderNs:order/orderNs:orderItem" namespace-map="orderNamespaceMap" />
|
||
</si-xml:xpath-splitter>
|
||
]]></programlisting>
|
||
</para>
|
||
<para>
|
||
A service activator is then used to pass the message into a stock checker POJO. The order item document is enriched with information
|
||
from the stock checker about order item stock level. This enriched order item message is then used to route the message. In the
|
||
case where the order item is in stock the message is routed to the warehouse. The XPath router makes use of a
|
||
<classname>MapBasedChannelResolver</classname> which maps the XPath evaluation result to a channel reference.
|
||
<programlisting language="xml"><![CDATA[<si-xml:xpath-router id="instockRouter" channel-resolver="mapChannelResolver"
|
||
input-channel="orderRoutingChannel" resolution-required="true">
|
||
<si-xml:xpath-expression expression="/orderNs:orderItem/@in-stock" namespace-map="orderNamespaceMap" />
|
||
</si-xml:xpath-router>
|
||
|
||
<bean id="mapChannelResolver"
|
||
class="org.springframework.integration.channel.MapBasedChannelResolver">
|
||
<property name="channelMap">
|
||
<map>
|
||
<entry key="true" value-ref="warehouseDispatchChannel" />
|
||
<entry key="false" value-ref="outOfStockChannel" />
|
||
</map>
|
||
</property>
|
||
</bean>
|
||
]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Where the order item is not in stock the message is transformed using
|
||
xslt into a format suitable for sending to the supplier.
|
||
<programlisting language="xml"><![CDATA[<si-xml:xslt-transformer input-channel="outOfStockChannel" output-channel="resupplyOrderChannel"
|
||
xsl-resource="classpath:org/springframework/integration/samples/xml/bigBooksSupplierTransformer.xsl"/>
|
||
]]></programlisting>
|
||
</para>
|
||
</section>
|
||
</section>
|
||
|
||
</appendix>
|