I recently had the pleasure of producing nine sample apps for Cloud Foundry, whilst embedded in the documentation team. The aim of the exercise was to produce minimal but functional apps, ready to be deployed to CF, in a variety of languages and frameworks. The process of writing these apps was pretty interesting, and broadened my knowledge of today’s popular languages and frameworks and their out-of-the-box compatibility with a PaaS like CF.
We ended up with the following sample apps. Check out the GitHub READMEs for instructions on running them both locally and on CF (Pivotal Web Services is used as an example).
|Golang||None (gorilla/mux routing)||MySQL||pong_matcher_go|
The apps all pass the same set of acceptance tests (written in Ruby), with varying degrees of cheating going on. They implement part of an API for an imaginary app that matches ping pong players. Sort of like Tinder for ping pong. It represents a bit of our culture at Pivotal.
Code quality disclaimer
Writing the same app over and over again: ensuring consistency
We made a decision early on to produce the same app in various different languages. This would make testing easier, and it would be easier to know when an app was up to parity with the rest. A simple API is ideal for this, as it’s easy to test in a black-box, acceptance-test style, without having to couple the test code to the language or framework.
I did end up writing some unit tests where it felt appropriate, or useful for learning. These tests were obviously written in the same language as the production code. I resisted the urge to write exhaustive unit tests, because I had a lot of languages to get through.
Porting versus re-writing
Being a Ruby developer at heart, the first implementation of the app was in Sinatra. I started out with a mutable Hash as a persistence mechanism. When my Product Manager requested persistence across reboots, I wrote an adapter that used the same Hash interface but persisted to Redis. I knew from the outset that Ruby’s operator overloading would make this migration possible and easy to do. Knowing that Groovy/Ratpack was coming up next, I knew that it’d be a straightforward port (hence why the Ratpack implementation also uses Redis). However, when my PM requested examples of running database migrations on CF, we had to switch guns to SQL.
The first implementation to use SQL as a persistence layer was Grails. It was somewhat frustrating that I couldn’t copy+paste the Ratpack implementation into Grails. However, it posed an interesting problem: up to now I had been retrieving a whole ‘table’ from Redis, and filtering in memory (hey – it’s just an example!). Doing the same from a SQL database just felt wrong. Nevertheless, I let it pickle for a while, and implemented the full table load with in-memory filter in both the Grails and Spring Boot versions of the app.
Once I’d done this a couple of times, and when I got to a language I felt naturally comfortable in, which happened to be Go, I decided to reimplement using a more normalised database schema. This approach then got ported to the PHP and Python versions. The way in which the solution to the problem posed by the acceptance tests changed as I drifted across languages and frameworks might have been a result of rewriting, but I also had a feeling that different frameworks were influencing me in different ways.
For the Go app, I didn’t use a framework. Did this give me the space to rethink the problem? Interestingly, the solution that I arrived at in the Go app worked well in the future apps that wanted me to use the Active Record pattern.
12 factor fails
One of the consistently annoying aspects of getting the apps to run on CF centred around the frameworks’ ideas about configuration across environments. A 12 factor app destined for the cloud has a simple requirement: “Let me set my URIs, credentials, memory limits and the like with environment variables. I don’t want to commit and push those thank you, I’ll let my PaaS keep hold of them.” Unfortunately, not every framework makes it easy to use environment variables out-of-the-box, and some make it quite challenging.
More than one framework needed custom code to support the DATABASE_URL environment variable, common to both Heroku and CF. See the Django and Slim versions for a taste of this special kind of pain. In some situations, the support was there, but implemented in the spirit of YAGNI such that it only worked with certain service providers. For example, whilst writing the Sails.js app, I had to fix mysql2:// URI parsing so that cleardb instances would work.
The Go buildpack on Pivotal Web Services: it’s now a bit easier
Before I began the Go app, I pushed an even smaller app to test the waters. Unfortunately, a push of this app without a manifest.yml did not succeed on PWS, because the default buildpack assumed that Go had been packaged and sent up with the app. Thankfully we managed to push through some updates to the buildpack on to PWS, so it’s now possible to push an extremely minimal Go app, without worrying about a manifest.yml.
Have you ever attempted to push a simple app to CF and run into issues? If so, we’d love to tackle those as part of the development of these sample apps. Also, if you want a sample app for a particular language or framework, we’d love to hear about it.
About the AuthorMore Content by Andrew Bruce