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. When finished, we’ll have an app to create and list cute kittens.

Create the database:

$ cf create-service mongodb-2 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-2` 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-go-app my-mongodb
Binding service my-mongodb to app my-go-app in org MyOrg / space MySpace as user@mydomain.com...
OK
TIP: Use 'cf restage my-go-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-go-app
Restaging app my-go-app in org MyOrg / space MySpace as user@mydomain.com...
-----> Downloaded app package (8.0K)
-------> Buildpack version 1.5.8
-----> Creating runtime environment

...

Now we want to consume our new MongoDB from within our application. Use glide to add the mgo and the go-cfenv packages to your dependencies:

$ glide get gopkg.in/mgo.v2 github.com/cloudfoundry-community/go-cfenv
[INFO] Preparing to install 2 packages.

  ...

Now edit your main.go file to use these packages to connect to the database specified in your VCAP_SERVICES environment variable. We do this by importing the packages and adding a route for the /kittens endpoint which returns the kittens collection of our MongoDB.

First, add the needed dependencies to your import statement:

import (
  "encoding/json"
  "fmt"
  "io"
  "io/ioutil"
  "log"
  "net/http"
  "os"

  "github.com/cloudfoundry-community/go-cfenv"

  "gopkg.in/mgo.v2"
  "gopkg.in/mgo.v2/bson"
)

Now we need to know what a kitten looks like. Create a struct for them:

// Kitten is a cute kitty
type Kitten struct {
  ID   bson.ObjectId `bson:"_id,omitempty" json:"_id"`
  Name string        `json:"name"`
}

// Kittens is an array of kittens
type Kittens []Kitten

Then we create another struct to hold a server with an mgo session:

// Server is a server with an mgo session
type Server struct {
    db *mgo.Session
}

As a next step, we need to talk to our database that we created above. In the main function, Create a variable dbString and dynamically assign it to the value of our db’s connection string. We then create a session from that and attach the session to our server s. This server allows us to inject the session into the kitten handler so that we don’t have to call Dial on every request. At the bottom, we add the kitten handler to handle the /kittens endpoint. Your main function should now look something like this:

func main() {
    var dbString string
    if vcapServices := os.Getenv("VCAP_SERVICES"); len(vcapServices) == 0 {
        dbString = "localhost"
    } else {
        appEnv, err := cfenv.Current()
        if err != nil {
            log.Fatal(err)
        }
        dbService, err := appEnv.Services.WithName("my-mongodb")
        if err != nil {
            log.Fatal(err)
        }
        uri, ok := dbService.Credentials["uri"].(string)
        if !ok {
            log.Fatal("no valid databse URI found")
        }
        dbString = uri
    }
    session, err := mgo.Dial(dbString)
    if err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    s := Server{db: session}

    http.HandleFunc("/", IndexHandler)
    http.HandleFunc("/kittens", s.KittenHandler)

    var port string
    if port = os.Getenv("PORT"); len(port) == 0 {
        port = "8080"
    }
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

The line if vcapServices := os.Getenv("VCAP_SERVICES"); len(vcapServices) == 0 { allows you to check if the app is running in the cloud. If not, it’s using a local MongoDB and if it is, it’s using the one from VCAP_SERVICES through the cfenv package.

Then we create the handler for our new /kittens endpoint. It handles either GET or POST requests. Create the following function:

// KittenHandler allows to manage kittens
func (s *Server) KittenHandler(w http.ResponseWriter, r *http.Request) {
    sess := s.db.Clone()
    defer sess.Close()
    c := sess.DB("").C("kittens")

    switch r.Method {

    case "GET":
        var kittens Kittens

        err := c.Find(bson.M{}).All(&kittens)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        w.Header().Set("Content-Type", "application/json; charset=utf-8")

        err = json.NewEncoder(w).Encode(kittens)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

    case "POST":
        var kitten Kitten

        err := json.NewDecoder(r.Body).Decode(&kitten)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        err = c.Insert(kitten)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        w.Header().Set("Content-Type", "application/json; charset=UTF-8")
        w.WriteHeader(http.StatusCreated)

        fmt.Fprintf(w, "Kitten '%s' created", kitten.Name)

    default:
        http.Error(w, "Not Found", http.StatusNotFound)
    }
}

This ensures that when you access your app using the /kittens route, it will return all the kittens stored in your database. Currently there are no kittens so it will return an empty array. Sad…

But you can create new kittens by POST-ing to the /kittens endpoint with the kitten’s name as a URL query parameter. You can do so using curl or any similar tool:

$ curl -X POST "http://localhost:8080/kittens" -d '{"name":"garfield"}' --header "Content-Type: application/json"

and then retrieve your new kitten at the GET /kittens endpoint.

Now, all you need to do is push your app to the cloud:

$ cf push my-go-app

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.

I’ve bound a service to my App
View the source for this page in GitHub