In the two previous posts in this series, we discussed using Cloud Foundry’s new support for standalone apps to deploy worker processes. We looked at an example using Resque for Ruby apps. In this third installment, we explore using Spring to create workers in Java apps.
Let’s walk through an example.
Deploying the Cloud Foundry Twitter Search Sample
Cloud Foundry Twitter Search includes two applications: a standalone Java application that periodically polls Twitter for tweets containing the word “cloud” and a Node.js web application that displays the results. The applications communicate via a shared RabbitMQ service. The worker publishes tweet information to a RabbitMQ exchange, and the web application consumes the tweets and pushes them to the browser using SockJS.
Let’s clone the sample app from Github:
mycomp:dev$ git clone https://github.com/cloudfoundry-samples/twitter-rabbit-socks-sample mycomp:dev$ cd twitter-rabbit-socks-sample/twitter2rabbit
The worker app is written with Spring Integration. In fact, all of the worker’s logic is contained in a single Spring context file.
mycomp:twitter2rabbit$ more src/main/resources/org/springsource/samples/twitter/context.xml
<?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:int="http://www.springframework.org/schema/integration" xmlns:int-twitter="http://www.springframework.org/schema/integration/twitter" xmlns:int-amqp="http://www.springframework.org/schema/integration/amqp" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xmlns:cloud="http://schema.cloudfoundry.org/spring" xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration/twitter http://www.springframework.org/schema/integration/twitter/spring-integration-twitter-2.1.xsd http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp-2.1.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd http://schema.cloudfoundry.org/spring http://schema.cloudfoundry.org/spring/cloudfoundry-spring-0.8.xsd"> <int-twitter:search-inbound-channel-adapter id="twitter" query="cloud"> <int:poller fixed-rate="5000" max-messages-per-poll="10"/> </int-twitter:search-inbound-channel-adapter> <int:transformer input-channel="twitter" expression="payload.fromUser + ': ' + payload.text" output-channel="rabbit"/> <int-amqp:outbound-channel-adapter id="rabbit" exchange-name="tweets"/> <rabbit:fanout-exchange name="tweets" durable="false"/> <rabbit:admin connection-factory="rabbitConnectionFactory"/> <rabbit:template id="amqpTemplate" connection-factory="rabbitConnectionFactory"/> <beans profile="default"> <rabbit:connection-factory id="rabbitConnectionFactory"/> </beans> <beans profile="cloud"> <cloud:rabbit-connection-factory id="rabbitConnectionFactory"/> </beans> </beans>
Using Spring Integration, we’ve set up an Inbound Twitter Channel Adapter to query Twitter for the word “cloud” every five seconds. The user and text from each tweet is published to an AMQP exchange named “tweets.” The connection to Rabbit is controlled by the choice of “default” or “cloud” profile. The cloud profile uses the cloud namespace provided by the cloudfoundry-runtime library to create a connection to a single Rabbit service bound to the app. The only code in this project is a single class that activates the cloud profile and bootstraps the ApplicationContext.
In order to run this example on Cloud Foundry, we’ll need to package up all of the dependencies. Enter the Maven Application Assembler Plugin.
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>appassembler-maven-plugin</artifactId> <version>1.1.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>assemble</goal> </goals> <configuration> <assembledirectory>target</assembledirectory> <programs> <program> <mainClass>org.springsource.samples.twitter.Demo</mainClass> </program> </programs> </configuration> </execution> </executions> </plugin> </plugins> </build>
The generated start script, target/appassembler/bin/demo, uses the JAVA_OPTS environment variable to pass options to Java. When this app is deployed, Cloud Foundry will set JAVA_OPTS to a min and max heap size based on the memory reservation we provide. If we are running against a local cloud, remote debug options will also be added to JAVA_OPTS if we push or start the app with –debug. That’s right, you can remote debug your standalone Java applications with your favorite IDE.
Now we’ll do a “mvn package” and we’re ready to deploy to Cloud Foundry from our new distribution directory:
mycomp:twitter2rabbit$ vmc push twitter2rabbit --path=target/appassembler Detected a Standalone Application, is this correct? [Yn]: 1: java 2: node 3: node06 4: ruby18 5: ruby19 Select Runtime : Selected java Start Command: bin/demo Application Deployed URL [None]: Memory reservation (128M, 256M, 512M, 1G, 2G) [512M]: How many instances? : Create services to bind to 'twitter2rabbit'? [yN]: y 1: mongodb 2: mysql 3: postgresql 4: rabbitmq 5: redis What kind of service?: 4 Specify the name of the service [rabbitmq-f7939]: twitter-rabbit Create another? [yN]: Would you like to save this configuration? [yN]: y Creating Application: OK Creating Service [twitter-rabbit]: OK Binding Service [twitter-rabbit]: OK Uploading Application: Checking for available resources: OK Processing resources: OK Packing application: OK Uploading (32K): OK Push Status: OK Staging Application 'twitter2rabbit': OK Starting Application 'twitter2rabbit': OK
VMC has detected that twitter2rabbit is a standalone application, and we selected the Java runtime. We specify the command “
bin/demo” to start the server using the generated script. We saved the manifest file for future deployments. The app should now be running and publishing tweets to an exchange on the twitter-rabbit service.
Now let’s deploy the front-end Node.js web application to display these tweets.
mycomp:twitter2rabbit$ cd ../rabbit2socks mycomp:rabbit2socks$ npm install mycomp:rabbit2socks$ vmc push mytwittersearch --runtime=node06 Detected a Node.js Application, is this correct? [Yn]: Application Deployed URL [mytwittersearch.cloudfoundry.com]: Memory reservation (128M, 256M, 512M, 1G, 2G) [64M]: How many instances? : Bind existing services to 'mytwittersearch'? [yN]: y 1: twitter-rabbit Which one?: 1 Create services to bind to 'mytwittersearch'? [yN]: Would you like to save this configuration? [yN]: y Creating Application: OK Binding Service [twitter-rabbit]: OK Uploading Application: Checking for available resources: OK Processing resources: OK Packing application: OK Uploading (18K): OK Push Status: OK Staging Application 'mytwittersearch': OK Starting Application 'mytwittersearch': OK
The application is pushed and bound to the same twitter-rabbit service as the Java worker, twitter2rabbit. The app will consume tweets from this Rabbit service and push them to the browser using SockJS. Let’s launch the website and watch as tweets start popping up! Here’s a screenshot of the Twitter traffic when I ran my app.
And there you have it! The web page is dynamically updated with results from the Spring Integration-powered worker app. Clone the sample application and try it yourself. The readme also contains instructions on building and deploying with Gradle (using the Gradle Application Plugin to create a distribution) instead of Maven.
There are many other ways to use Spring to create worker apps, including using the Spring Task Scheduler abstraction or Spring Batch. For more examples of Spring workers on Cloud Foundry, check out Josh Long’s post on the SpringSource blog.
In the next and final post of the series, we will look at another category of standalone apps: self-executing web applications. With standalone application support, the possibilities are endless.
- Jennifer Hickey
The Cloud Foundry Team
Don’t have a Cloud Foundry account yet? Sign up for free today