Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreTraditionally, applications have been defined by the principle technology they use. If you're building a Spring MVC application, we call it a "Java app". Since our application is primarily composed of Java components, we tend to stay in our own yards and not be terribly friendly with our neighbors until we're forced to interact with them. We set up Java-based application servers and tend to think first of going to the Java language to solve a problem in our application whether that language is the best choice or not. It has usually just been too difficult to maintain multiple sets of runtime environments for our applications, so we stovepipe ourselves through sheer inertia.
Cloud Foundry turns that dynamic on its head because it is no longer inconvenient to use the right tool for the job. We simply are not forced to stovepipe our applications into one species any more (a "Java app" or a "Node app"). If we need really high volume, non-blocking throughput with XHR long-polling support, we can use Node.js for that portion of our application. If we need the flexibility and depth of library support found in the Spring family of projects, we can easily take advantage of them by using Java for that portion of the application. If we need both a fast key-value store for a cache or event bus and a capable document store for persisting data, we can use both in the same application without worrying about the logistics involved in setting these services up separately and managing them ourselves (or dumping them onto our already-harried Operations staff).
It also doesn't hurt our case any that deploying either species of application is as simple as issuing a "push" command from our favorite shell.
What's that old saying? "If it's worth doing, it's worth overdoing"? This example application is a poster child for that sentiment!
There are several components to this application:
Numbers 1 and 2 are handled in the same application: namely the Node.js app that powers the web front-end. Number 3 is a standard Spring MVC application that uses the NoSQL support of the Spring Data family of projects to connect to Redis and MongoDB in a single helper class.
We're using Node.js because: a) it's lightweight, fast, and non-blocking, and: b) it's the zippered hoodie of the web world (it's what all the cool kids are wearing these days).
In all seriousness, Node.js is an excellent choice in which to deploy a web front-end. We're using it to asynchronously send ticker events to the browser using Socket.IO, to the database using the Mongoose MongoDB library, and over our application's event bus (in this case, Redis) to be consumed by code running in another application.
There's a lot here, so we'll take each part in chunks.
Before we get too deep into the application, we need to discuss how to get configuration information from the Cloud Foundry environment. The hostname, port, user, and password you need to connect to your provisioned services is encoded into a JSON document stored in an environment variable named "VCAP_SERVICES". There are various helper utilities springing up to aid the developer in using these configuration values (or defaults when running your app locally). The Node.js module we'll be using here will not necessarily reflect the official Node.js Cloud Foundry runtime module that is being worked on as we write this.
To get the configuration information you need to connect to your MongoDB instance when running in Cloud Foundry, require the "cloudfoundry" module like so:
var cf = require("cloudfoundry");
var mongoConfig = cf.getServiceConfig("ticker-analysis")
|| { username: "admin", password: "password", hostname: "localhost", port: 27017, db: "tickeranalysis" };
This either pulls our configuration information from the VCAP_SERVICES environment variable or provides a set of defaults for use when running locally.
We're using the Mongoose MongoDB mapping library for connecting to our database. We save our individual ticker events as well as read those saved by the Spring MVC application. The nice thing about using a document store to persist our data is that it gives us full cross-language support. We can save an object using the Spring Data mapping infrastructure and later read that object into our Node.js application using Mongoose.
To configure the Mongoose library, we need to define our models:
var mongoose = require("mongoose"),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId,
DocumentObjectId = mongoose.Types.ObjectId;
var TickerEvent = new Schema({
symbol: { type: String },
price: { type: Number },
volume: { type: Number }
});
mongoose.model('TickerEvent', TickerEvent);
var TickerSummary = new Schema({
_id: { type: String },
timestamp: { type: Number },
max: { type: Number },
min: { type: Number },
average: { type: Number },
volume: { type: Number }
});
mongoose.model('TickerSummary', TickerSummary);
The corresponding domain object on the Java side looks like this:
@Document(collection = "tickersummary")
public class Summary {
@Id
private final String symbol;
private final Long timestamp;
private Float total = new Float(0);
private Integer samples = 0;
private Float min = Float.MAX_VALUE;
private Float average = new Float(0);
private Float max = Float.MIN_VALUE;
private Integer volume = 0;
// Constructors, getters, and setters...
}
To power the web front-end, we'll be using the express.js web framework for Node.js. Of note in this block is our use of a special method on the Cloud Foundry Node.js module to tell us whether we're running in the cloud or not. If we are, then we don't want to dump our exceptions to the browser like we do when we're running in development.
var express = require("express");
var app = express.createServer();
app.configure(function() {
// Standard express setup
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
// Use the Jade template engine
app.set('view engine', 'jade');
app.set('running in cloud', cf.isRunningInCloud());
// Don't give away information about our environment in production
if(!cf.isRunningInCloud()) {
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
}
});
We're using Socket.IO for Ajax long-polling to pipe our server-side events to a listening browser. Since Cloud Foundry is only in beta, it doesn't yet support full-blown websockets (it's on the roadmap). To set this up, we're going to specify that Socket.IO use long polling since we already know the dynamic routing infrastructure won't be happy with us otherwise. We also have to reset this connection after 10 seconds to keep the timeout police from confiscating our connections. As the Cloud Foundry platform evolves, this will likely be a moot point. But for the time being, just keep these caveats in mind if using Ajax push with Cloud Foundry.
var io = require("socket.io").listen(app, {
transports: ['xhr-polling'],
transportOptions: {
'xhr-polling': {duration: 10000}
}
});
To generate the actual data points, we could have chosen to subscribe to any of the publicly-available stock ticker feeds. Since it doesn't actually matter in this case how the data is constituted and we get a better chance to illustrate cross-runtime integration at a deeper level by doing so, we're going to randomly generate our ticker data.
To publish these events to another listening application, we need to use Redis' pub/sub functionality as an event bus. To do that in Node.js, we set up two separate Redis client instances. One will be used for listening for events to send to the browser and the other will be the outbound publisher client.
// Get our Cloud Foundry config information or default to localhost
var redisConfig = cf.getServiceConfig("ticker-stream")
|| { hostname: "localhost", port: 6379, password: false };
// Create Redis client instances
var redisClient = redis.createClient(redisConfig.port, redisConfig.hostname);
var redisPublisher = redis.createClient(redisConfig.port, redisConfig.hostname);
if(redisConfig.password) {
// Cloud Foundry Redis instances are secured with a password
redisClient.auth(redisConfig.password);
redisPublisher.auth(redisConfig.password);
}
redisClient.subscribe("ticker-stream");
redisClient.on("message", function(channel, json) {
var data = JSON.parse(json);
// Save this event to the database
var TickerEvent = db.model('TickerEvent', 'tickerdata');
var te = new TickerEvent({
symbol: data.symbol,
price: data.price,
volume: data.volume
});
te.save(function(err) {
if(err) {
throw(err);
}
});
// Broadcast this event to the browser
io.broadcast(json);
});
To send the data, we have a helper method that we call setTimeout on and pass a random wait time of 3-7 seconds.
var tickerSender;
function sendTickerEvent() {
var symbolInfo = {
symbol: getRandomSymbol(),
price: getRandomPrice(),
volume: getRandomVolume()
};
redisPublisher.publish("ticker-stream", JSON.stringify(symbolInfo));
// Call ourselves again after 3-7 seconds
tickerSender = setTimeout(sendTickerEvent, getRandomTimeout());
}
Our routes for the web application are pretty sparse. We need to render the home page with the Javascript magic in it to power the UI and provide a route for getting the summary document from MongoDB to display on the right-hand side of the page when the user clicks on a ticker symbol link.
app.get("/", function(req, resp) {
resp.render("home", {
pageTitle: "Ticker Analysis Sample"
});
});
app.get("/summary/:symbol", function(req, resp) {
var TickerSummary = db.model("TickerSummary", "tickersummary");
TickerSummary.findById(req.params.symbol, function(err, data) {
if(err) {
// Handle error
}
resp.send(JSON.stringify(data));
});
});
To initialize our data generation, we need to make sure our random event emitter is running. But since we don't want our database to fill up when no one's looking at the page, we'll just start the event emitter the first time a user hits our application. After that, we'll just leave it running until the timeout "tickerSender" is cleared (you can add a route to do that, if you want).
// Socket.IO-based Ticker Stream
io.on("connection", function(client) {
if(!tickerSender) {
// Start the ticker stream if one hasn't been already
sendTickerEvent();
}
});
To tell Express.js what port our application should run on, we need to read the environment variable VCAP_APP_PORT. There's another method on our Cloud Foundry Node.js module to do that for us. So our call to listen() looks like this:
app.listen(cf.getAppPort());
We could keep this in the family and handle the summary calculations in Node.js. But sometimes there's very good business reasons for using a Java/Spring component for a portion of your application. Our purpose here is to illustrate how this can be done so you can choose the right tool for the job.
You'll remember when we were dealing with the Node.js portion that we had to get configuration parameters from our environment when running in Cloud Foundry. Our Spring application has the same need. But since there's already a capable Cloud Foundry runtime library for Java, we'll use that to extract the bits we need to connect to our provisioned MongoDB instance.
The first thing we need to do is declare a couple additional namespaces. One for the Cloud Foundry runtime, and one for the MongoDB support.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cloud="http://schema.cloudfoundry.org/spring"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://schema.cloudfoundry.org/spring http://schema.cloudfoundry.org/spring/cloudfoundry-spring-0.6.xsd
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
In particular, note that we're going to be using Spring 3.1, which is still in pre-release status. You don't have to use Spring 3.1 to use Cloud Foundry. But the <a href="http://blog.springsource.com/2011/02/14/spring-3-1-m1-introducing-profile/" "Blog post about profiles">profiles feature of Spring 3.1 will make our configuration easier.
To configure our MongoDB connection, we'll use the <mongo:mongo/> namespace configuration helper when running locally, and it's cousin, the <cloud:mongo/> namespace configuration helper when running in the cloud. In our "default" profile, we'll set up some properties to mimic those we'll have available when running in the cloud--we'll just set them to our local MongoDB server.
<!-- Use this when running locally -->
<beans profile="default">
<util:properties id="serviceProperties">
<prop key="ticker-analysis.db">tickeranalysis</prop>
<prop key="ticker-analysis.username">admin</prop>
<prop key="ticker-analysis.password">passwd</prop>
</util:properties>
<mongo:mongo id="mongo"/>
<bean id="redisConnectionFactory"
class="org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory"/>
</beans>
<!-- Use this when running in the cloud -->
<beans profile="cloud">
<cloud:service-properties id="serviceProperties"/>
<cloud:mongo id="mongo"/>
<cloud:redis-connection-factory id="redisConnectionFactory"/>
</beans>
<!-- MongoDB -->
<mongo:mapping-converter id="mappingConverter"/>
<bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate"
p:username="#{serviceProperties['ticker-analysis.username']}"
p:password="#{serviceProperties['ticker-analysis.password']}">
<constructor-arg ref="mongo"/>
<constructor-arg name="databaseName" value="#{serviceProperties['ticker-analysis.db']}"/>
<constructor-arg name="defaultCollectionName" value="tickerdata"/>
<constructor-arg ref="mappingConverter"/>
</bean>
The properties for our provisioned services, as you will notice, follow the convention SERVICE_NAME.PROPERTY_NAME. In this example, I have a MongoDB service provisioned with the name "ticker-analysis":
> vmc services
============== System Services ==============
... [omitted for brevity]
=========== Provisioned Services ============
+-----------------+---------+
| Name | Service |
+-----------------+---------+
| ticker-stream | redis |
| ticker-analysis | mongodb |
+-----------------+---------+
As you might be able to guess now, my Redis connection follows a similar pattern.
Astute readers will immediately wonder: "but how does it know which profile to use?" In our case, we'll be using an ApplicationContextInitializer that sets our profile based on whether or not the proper environment variables are available.
Here's all we need to set our profile at runtime so we can run with the "default" profile during development and the "cloud" profile when running in Cloud Foundry:
public class CloudApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CloudEnvironment env = new CloudEnvironment();
if (env.getInstanceInfo() != null) {
// We're running in the cloud, set the profile accordingly
applicationContext.getEnvironment().setActiveProfiles("cloud");
}
else {
applicationContext.getEnvironment().setActiveProfiles("default");
}
}
}
To activate this ApplicationContextInitializer, we add it to our web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>org.cloudfoundry.services.CloudApplicationContextInitializer</param-value>
</context-param>
</web-app>
Our Spring layer is pretty simple. We have a helper class that leverages the MessageListenerAdapter in the Spring Data Redis support. Our bean will be invoked whenever Redis gets a message for that event. Inside that handler, we'll be using the Spring Data MongoDB support to map a POJO onto that document so we can update the min, max, and average values.
public void handleMessage(String json) throws IOException {
// Use the Jackson ObjectMapper to turn a JSON document into a POJO
TickerEvent event = mapper.readValue(new StringReader(json), TickerEvent.class);
// Load the existing document or start a new one
Summary summ = mongoTemplate.findOne(query(where("_id").is(event.getSymbol())), Summary.class);
if (null == summ) {
summ = new Summary(event.getSymbol(), System.currentTimeMillis());
}
// Recalculate min, max, and average
summ.addTickerEvent(event);
// Save the modified document back
mongoTemplate.save(summ);
}
We don't need to expose anything in our Spring layer to the web. It does its work offline, doesn't require input from the user, and doesn't provide the summary data directly to a web client.
That being said, we might want to put a simple Controller in that will let us know what's going on inside our Java helper class. We created just such a class in our sample application.
@Controller
@RequestMapping("/summaries")
public class SummariesController {
@Autowired
private SummaryService summaryService;
@RequestMapping(value = "/", method = RequestMethod.GET)
public @ResponseBody List<Summary> summaries() {
// Return all summaries
return summaryService.getSummaries();
}
@RequestMapping(value = "/{symbol}", method = RequestMethod.GET)
public @ResponseBody Summary summary(@PathVariable String symbol) {
// Return a specific summary document
return summaryService.getSummary(symbol);
}
}
This is not something you'd want to do in a production application. But for developing on Cloud Foundry and getting some insight into what sometimes feels like a black box, it might make sense to put in a few controller methods that expose your Spring layer's innards.
I don't know about you, but I get a little bored with the simplistic nature of many examples and tutorials. Admittedly, there's nothing simplistic about this sample application! It might seem a little like drinking from a fire hose here, but the goal was to provide you with enough meat on the bones to keep you busy for a while as you investigate Cloud Foundry and get your cloud [sea] legs.
The example app is live on Cloud Foundry:
All the source code is in the Cloud Foundry samples repo on GitHub:
For help getting up-to-speed with Cloud Foundry, you can access the forums:
Comprehensive documentation is still in flux because, frankly, so is the Cloud Foundry platform. It's a bit of a moving target at the moment. The community is maintaining some wiki pages on github, though, which should help some.
The Node.js module referenced earlier (to provide easier access to the Cloud Foundry environment variables) is actually part of the sample application until a full-blown Cloud Foundry runtime is released for Node.js.
Happy hacking!