Bind a Service to Your App
Page last updated:
Page last updated:
The service marketplace has a large number of data stores, from Redis and MongoDB, to MariaDB (fork of MySQL) and RabbitMQ. You can run cf marketplace
to get an overview. In this step you will add a small MongoDB database to your app.
Create the database:
$ cf create-service mongodb small my-mongodb Creating service instance my-mongodb in org MyOrg / space MySpace as user@mydomain.com... OK Create in progress. Use 'cf services' or 'cf service my-mongodb' to check operation status. Attention: The plan `small` of service `mongodb` is not free. The instance `my-mongodb` will incur a cost. Contact your administrator if you think this is in error.
This creates a small MongoDB database for you which we now have to bind to our application. Binding means that the credentials and URL of the service will be written dynamically into the environment variables of the app as VCAP_SERVICES
and can hence be used directly from there.
Let’s bind the new service to our existing application:
$ cf bind-service my-clojure-app my-mongodb Binding service my-mongodb to app my-clojure-app in org MyOrg / space MySpace as user@mydomain.com... OK TIP: Use 'cf restage my-clojure-app' to ensure your env variable changes take effect
Note: If you are getting Server error, status code: 409
, please try again after a couple of minutes. It takes a while to spin up that MongoDB for you.
After that we restage the application as suggested so that it includes the new credentials in its environment variables:
$ cf restage my-clojure-app Restaging app my-clojure-app in MyOrg monitor / space MySpace as user@mydomain.com... -----> Downloaded app buildpack cache (4.0K) -----> Java Buildpack Version: v3.6 (offline) | https://github.com/cloudfoundry/java-buildpack.git#5194155 -----> Downloading Open Jdk JRE 1.8.0_71 from https://download.run.pivotal.io/openjdk/trusty/x86_64/openjdk-1.8.0_71.tar.gz (found in cache) ...
Now we want to consume our new MongoDB from within our application. In order to do that, we will create a simple to-do list and store the tasks in the MongoDB. We also want to display all tasks as an ordered list. We have to add the library Monger to the dependencies by adding it to the array of dependencies in the file project.clj
:
[com.novemberain/monger "3.1.0"]
In addition, we need to use two more libraries to be able to get and correctly parse the VCAP_SERVICES
environment variable: the library Environ to get the VCAP_SERVICES
variable and the library Cheshire to parse the JSON variable to a Clojure map. Add these to the dependencies array as well:
[environ "1.1.0"]
[cheshire "5.6.3"]
Now the project.clj
file should look something like this:
(defproject cf-sample-app-clojure "0.1.0-SNAPSHOT"
:description "Sample Clojure application for Cloud Foundry"
:author "Ivan Baldinotti"
:email "ivan.baldinotti@swisscom.com"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.8.0"]
[compojure "1.5.1"]
[ring/ring-defaults "0.2.1"]
[ring/ring-jetty-adapter "1.5.0"]
[hiccup "1.0.5"]
[com.novemberain/monger "3.1.0"]
[environ "1.1.0"]
[cheshire "5.6.3"]]
:plugins [[lein-ring "0.9.7"]]
:ring {:handler cf-sample-app-clojure.core/app}
:main cf-sample-app-clojure.core
:aot [cf-sample-app-clojure.core])
Now edit your core.clj
file to create additional routes for our to-do list project.
In the function defroutes
you can add two additional routes, a route to display a form with a GET request and a route that accept POST requests. The function should look like this:
(defroutes app-routes
(GET "/" [] "I am awesome!")
(GET "/todos" [] (toDoPage))
(POST "/todos" [todo] (toDoPage todo))
(route/resources "/")
(route/not-found "Not Found"))
Additionally, to be able to execute POST requests, we need to disable a security check: the anti forgery CSRF token. Normally this is not advisable, but we do it to simplify the tutorial application. To disable the CSRF token, replace the app
definition in the core.clj
file with the following:
(def app
(wrap-defaults app-routes (assoc-in site-defaults [:security :anti-forgery] false)))
Important: Be aware that disabling the anti-forgery check makes your application potentially vulnerable to CSRF attacks. Therefore you should not do this for production applications. Check out this tutorial on how to implement a CSRF token for POST requests correctly.
As you can see, we created the additional routes for the GET and the POST requests on /todos
. Now, we need to add additional functions to implement those requests. We need to create the functions toDoPageInput
and toDoPageOutput
. We will also implement a more general function commonLayout
. All these functions use the templating library Hiccup for representing HTML in Clojure. It uses vectors to represent elements, and maps to represent an element’s attributes. With Hiccup it’s easy to dynamically create the HTML pages we need for our form and to display the list of tasks. To use Hiccup, we need to add the :use
keyword with the Hiccup functions we want to use to the name space function of the file core.clj
:
(ns cf-sample-app-clojure.core
(:gen-class)
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.adapter.jetty :as jetty]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]])
(:use [hiccup.core]
[hiccup.page]
[hiccup.form]
[hiccup.element]))
Now, we need to implement the general function to render the HTML page. We call this function commonLayout
and we need to place the following code in our core.clj
file right before the defroutes
block:
(defn commonLayout [& content]
(html5
[:head
[:meta {:charset "utf-8"}]
[:title "ListOfTasks"]
(include-css "/css/style.css")]
[:body content]))
The function accepts an argument that is the body of the HTML page. So we can use this function to display both the form and the list of tasks. To do that, we need to implement an additional function. This functions passes the body as an argument to the commonLayout
function. In addition, we need to define two additional functions that are needed to save and to get the tasks from the MongoDB. The functions saveToDo
and getToDo
Place the following code right between the commonLayout
function and the defroutes block
.
(defn createToDo [todo]
(saveToDB todo "todos"))
(defn findToDos []
(getAllFromDB "todos"))
(defn toDoPage [& [todo]]
(when todo
(createToDo todo))
(commonLayout
[:h2 "Todo List"]
(ordered-list (findToDos))
[:h2 "Enter a new ToDo"]
[:form {:method "post" :action "/todos"}
[:input.text {:type "text" :name "todo"}]
[:br]
[:br]
[:input.action {:type "submit" :value "New"}]]))
These two functions are simply calling two functions from another file mongodb.clj
, which we will create soon. In the file core.clj
we need to update the require list importing all the functions from the file mongodb.clj
. Here’s the complete name space function for the core.clj
file:
(ns cf-sample-app-clojure.core
(:gen-class)
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.adapter.jetty :as jetty]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[cf-sample-app-clojure.mongodb :refer :all])
(:use [hiccup.core]
[hiccup.page]
[hiccup.form]
[hiccup.element]))
Now create a file called mongodb.clj
in src/cf_sample_app_clojure
which will contain the implementation to use the monger
library and connect to our MongoDB.
We need to save the todo we get from the text input to the MongoDB. We can do this both locally and in the Cloud Foundry deployment. First, let’s try it locally (requires MongoDB to be installed). To do so, we create the function name space in the mongodb.clj
file:
(ns cf-sample-app-clojure.mongodb
(:require [monger.core :as mg]
[monger.collection :as mc])
(:import [org.bson.types ObjectId]
[com.mongodb DB WriteConcern]))
This snippet of code shows how to import the library monger
that we will use to access the MongoDB. Now we implement the private function connectToDB
to connect to the MongoDB. Add it to the bottom of mongodb.clj
:
(defn- connectToDB []
(let [credentials (getCredentials "mongodb")
uri (get credentials :uri "mongodb://127.0.0.1:27017/test")]
(mg/connect-via-uri uri)))
Next, we can implement the function saveToDB
that accepts thet todo as argument as well as the name of the collection we want to create. Add it to the bottom of mongodb.clj
:
(defn saveToDB [document coll]
(try
(let [{:keys [conn db]} (connectToDB)]
(println "Inserting document")
(mc/insert-and-return db coll {:todo document})
(println "Document inserted")
(println "Closing database")
(mg/disconnect conn))
(catch Exception e (println (str "caught exception: " (.getMessage e))))))
This function accepts the text document as an argument and inserts it into the local MongoDB database test
. The collection name todos
comes from the file core.clj
.
Then we need to implement the function getAllFromDB
that gets all the saved todos on the mongo database. Add it to the bottom of mongodb.clj
:
(defn getAllFromDB [coll]
(try
(let [{:keys [conn db]} (connectToDB)
all-documents-in-map (mc/find-maps db coll)
documents (doall (map #(get % :todo) all-documents-in-map))]
(mg/disconnect conn)
documents)
(catch Exception e (println (str "caught exception: " (.getMessage e))))))
This function gets all documents from the MongoDB as maps. Then it extracts all the todo
documents, creating a simple sequence from the lazy sequence returned by the map function. The simple sequence containing all the todo tasks is passed to the ordered-list
function of Hiccup that renders an HTML ordered list containing all the to-do tasks.
To make the app run on Cloud Foundry, we need to correctly parse the VCAP_SERVICES
environment variable. Go ahead and create a new file parse.clj
in src/cf_sample_app_clj
.
Let’s create the function getCredentials
that returns a map containing the credentials needed to connect to the MongoDB service. Add this code to prase.clj
:
(ns cf-sample-app-clojure.parse
(require [environ.core :refer [env]]
[cheshire.core :refer :all]))
(defn getCredentials [service]
(let [vcap_services (env :vcap-services)
json_services (parse-string vcap_services true)
db (get json_services (keyword service))]
(get (get db 0) :credentials)))
We use the libraries environ
and cheshire
to do this.
Now include the parse.clj
library we just created in the file mongodb.clj
. To do so, add it to the require
statement of said file:
(ns cf-sample-app-clojure.mongodb
(:require [monger.core :as mg]
[monger.collection :as mc]
[cf-sample-app-clojure.parse :refer :all])
(:import [org.bson.types ObjectId]
[com.mongodb DB WriteConcern]))
With this latest code we can still experiment locally, since the URI defaults to the local one in case the Cloud Foundry credentials don’t exist. This allows you to run your app locally as well as in the cloud without having to configure anything differently. So let’s push it to the cloud after recompiling:
$ lein uberjar $ cf push my-clojure-app -b java_buildpack -p target/cf-sample-app-clojure-0.1.0-SNAPSHOT-standalone.jar
Open /todos
in your web browser and add your todos.
You can access other services like Redis or MariaDB in a similar matter, simply by binding them to your app and accessing them through the environment variables.
You can checkout the finished version of all these changes in the final-result
branch of the sample app repository, at: https://github.com/swisscom/cf-sample-app-clojure/tree/final-result