Daniel Watrous on Software Engineering

A Collection of Software Problems and Solutions

Posts tagged services

Software Engineering

Microservices are not as new as you think

All around me architects and business managers are beginning to mandate that internal software applications be built (and sometimes rebuilt) as microservices so that we can reuse them and compose applications more quickly. I do admit that the idea of a curated catalog of well designed microservices is attractive. Contrary to all the buzz that this is a new approach and will produce huge efficiencies, there is a lot of history that points to serious barriers to a microservice economy.

Microservices are Not New

What is being referred to as microservices today is just another name for abstraction and isolation, or code reuse. The idea is that a service should be designed to solve a single problem well in a way that is general (or abstract) enough to be broadly useful. This service is then isolated from its consumers which simplifies development and releases. Concepts around code reuse have date back at least 45 years.

The same goals of abstraction, isolation and reuse were the focus of Enterprise JavaBeans (EJB), which released its original specification in 1997. Much like microservices, EJBs were adopted by large enterprises who wanted all the benefits of code reuse and rapid application development through a type of composition. Unfortunately the complexity of the EJB APIs, and the apparent difficulty in designing easy to use interfaces reduced the value to the developer. Developers considered EJBs as adding complexity without delivering tangible benefit.

Similar efforts toward reuse have emerged as language libraries, drivers, patterns and so on. Many SaaS solutions today deliver on this same promise at a higher level making it possible to compose Enterprises from a collection of SaaS offerings.

But microservices are language independent…

Most discussion of microservices focuses on a RESTful interface (HTTP based) rather than a language specific interface (EJB). While this can make a microservice programming language agnostic, it requires an application developer to translate between the HTTP interaction with the service and their application code. Microservices must also deliver on the promise of simplifying, securing and scaling a service that is sufficiently abstract to be broadly useful, yet specific enough to solve a non-trivial problem.

Start with real problems, then abstract

It’s uncommon to see the most appropriate and useful abstraction for broadly useful services at the beginning of a project. The most useful abstractions often emerge over time as similar problems/solutions find their way into many different applications. Identifying these opportunities for reuse should start with existing solutions to the problems in your enterprise, but that alone isn’t enough. When recurring problems/solutions are identified, they still need to be useful when generalized. Excessive parameterization and configuration can limit the usefulness of the service. In other words, a microservice needs to be at least as easy to use (probably easier), and deliver at least as much value (probably more) than directly implementing the same functionality.

Some examples of microservices that are probably a bad idea:

  • spell checking
  • string formatting
  • email validation

Efficient language specific solutions already exist and have low latency for the cases listed above. They are also likely to have application specific implementations that interfere with making them generally useful. These functions are also unlikely to have associated enterprise wide standards.

Some examples that might make good microservices:

  • Fraud detection
  • Software licensing
  • Authentication and Authorization

In these cases, there are likely to be business specific rules that are common to the whole enterprise, which is benefited by centralizing a service. These types of services do not complete with well established norms or existing language specific libraries. Similar services are also offered through vendors and as SaaS, which further supports the possibility that it could be broadly useful and non-trivial to implement directly.

Start with a monolithic application

One of my favorite authors, Martin Fowler, recently published a follow up article to his previously published discussion of microservices architecture, which suggests the best way to develop microservices is to start by developing monolithic applications.

As he points out, if you already have a clear view of the boundaries for your service and are convinced that it can stand on it’s own and be broadly useful, there’s no reason not to start with microservices. What should be avoided is pre-mature optimization which drives the solution independent of the problem being solved.

Treat everything as a real product

One technique to make consistent decisions is to have a product owner who can align possible solutions with the actual product goals. Creating a microservice is a type of segmentation. The product is being split into two products and the would be product owner for the new microservice should be able to identify his consumer’s needs and where his product fits.

Follow the old rules

If I’ve convinced you that microservices are just an exercise in abstraction, isolation and code reuse, then you might find a lot of value in going back to basics for tips on how to design applications. Some of my favorite books on the subject are

http://www.amazon.com/Expert-One—One-Design-Development/dp/0764543857
http://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420

Software Engineering

Managed services in Stackato

This post is an extension of Managed services in CloudFoundry and follows the discussion of external services integration. The echo service and python service broker implementation are deployed using the documented procedure for Stackato. If necessary, you can get a Stackato instance up and running quickly. The result should be two apps deployed as shown below.

stackato-service-broker-01

Optional validation

It is possible to validate that the deployed apps work as expected. The curl commands below validate the echo service is working properly.

watrous@watrous-helion:~$ curl -X PUT http://echo-service.stackato.danielwatrous.com/echo/51897770-560b-11e4-b75a-9ad2017223a9 -w "\n"
{"instance_id": "51897770-560b-11e4-b75a-9ad2017223a9", "dashboard_url": "http://localhost:8090/dashboard/51897770-560b-11e4-b75a-9ad2017223a9", "state": "provision_success"}
watrous@watrous-helion:~$ curl -X PUT http://echo-service.stackato.danielwatrous.com/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -w "\n"
{"state": "bind_success", "app": "myapp", "id": "51897770-560b-11e4-b75a-9ad2017223a9"}
watrous@watrous-helion:~$ curl -X POST http://echo-service.stackato.danielwatrous.com/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -H "Content-Type: application/json" -d '{"message": "Hello World"}' -w "\n"
{"response": "Hello World"}
watrous@watrous-helion:~$ curl -X POST http://echo-service.stackato.danielwatrous.com/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -H "Content-Type: application/json" -d '{"message": "Hello World 2"}' -w "\n"
{"response": "Hello World 2"}
watrous@watrous-helion:~$ curl -X POST http://echo-service.stackato.danielwatrous.com/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -H "Content-Type: application/json" -d '{"message": "Hello Worldz!"}' -w "\n"
{"response": "Hello Worldz!"}
watrous@watrous-helion:~$ curl -X GET http://echo-service.stackato.danielwatrous.com/echo/dashboard/51897770-560b-11e4-b75a-9ad2017223a9
 
<table class="pure-table">
    <thead>
        <tr>
            <th>Instance</th>
            <th>Bindings</th>
            <th>Messages</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>51897770-560b-11e4-b75a-9ad2017223a9</td>
            <td>1</td>
            <td>3</td>
        </tr>
    </tbody>
</table>
watrous@watrous-helion:~$ curl -X DELETE http://echo-service.stackato.danielwatrous.com/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -w "\n"
{"state": "unbind_success", "app": "myapp", "id": "51897770-560b-11e4-b75a-9ad2017223a9"}
watrous@watrous-helion:~$ curl -X DELETE http://echo-service.stackato.danielwatrous.com/echo/51897770-560b-11e4-b75a-9ad2017223a9 -w "\n"
{"state": "deprovision_success", "id": "51897770-560b-11e4-b75a-9ad2017223a9", "messages": 3, "bindings": 0}

The service broker API implementation can also be verified using curl as shown below.

watrous@watrous-helion:~$ curl -X GET http://service-broker.stackato.danielwatrous.com/v2/catalog -H "X-Broker-Api-Version: 2.3" -H "Authorization: Basic dXNlcjpwYXNz" -w "\n"
{"services": [{"plans": [{"name": "large", "description": "A large dedicated service with a big storage quota, lots of RAM, and many connections", "id": "big_0001", "free": false}], "description": "Echo back the value received", "id": "echo_service", "bindable": true, "name": "Echo Service", "dashboard_client": {"redirect_uri": "http://echo-service.stackato.danielwatrous.com/echo/dashboard", "id": "client-id-1", "secret": "secret-1"}}, {"plans": [{"name": "small", "description": "A small shared service with a small storage quota and few connections", "id": "small_0001"}], "description": "Invert the value received", "id": "invert_service", "bindable": true, "name": "Invert Service", "dashboard_client": null}]}
watrous@watrous-helion:~$ curl -X PUT http://service-broker.stackato.danielwatrous.com/v2/service_instances/mynewinstance -H "Content-type: application/json" -H "Authorization: Basic dXNlcjpwYXNz" -d '{"service_id": "echo_service", "plan_id": "small_0001", "organization_guid": "HP", "space_guid": "IT"}' -w "\n"
{"dashboard_url": "http://echo-service.stackato.danielwatrous.com/echo/dashboard/mynewinstance"}
watrous@watrous-helion:~$ curl -X PUT http://service-broker.stackato.danielwatrous.com/v2/service_instances/mynewinstance/service_bindings/myappid  -H "Content-type: application/json" -H "Authorization: Basic dXNlcjpwYXNz" -d '{"service_id": "echo_service", "plan_id": "small_0001", "app_guid": "otherappid"}' -w "\n"
{"credentials": {"uri": "http://echo-service.stackato.danielwatrous.com/echo/mynewinstance/myappid"}}
watrous@watrous-helion:~$ curl -X DELETE http://service-broker.stackato.danielwatrous.com/v2/service_instances/mynewinstance/service_bindings/myappid  -H "Authorization: Basic dXNlcjpwYXNz" -w "\n"
{}
watrous@watrous-helion:~$ curl -X DELETE http://service-broker.stackato.danielwatrous.com/v2/service_instances/mynewinstance -H "Authorization: Basic dXNlcjpwYXNz" -w "\n"
{}

Manage the Service Broker

Service brokers can be managed in Stackato using the command line client. After issuing the add-service-broker command, several prompts as for details about the service broker. The username and password values should be credentials for the service broker, not your stackato credentials. The python service broker used in this example will accept any combination of username and password. The brokers can then be listed out using the service-brokers command.

C:\Users\watrous\Documents\GitHub\cf-service-broker-python>stackato add-service-broker
Enter name: echo-broker
Enter url: http://service-broker.stackato.danielwatrous.com
Enter username: user
Enter password: pass
Creating new service broker [echo-broker] ... OK
 
C:\Users\watrous\Documents\GitHub\cf-service-broker-python>stackato service-brokers
+-------------+--------------------------------------------------+
| Name        | Url                                              |
+-------------+--------------------------------------------------+
| echo-broker | http://service-broker.stackato.danielwatrous.com |
+-------------+--------------------------------------------------+

Make the service plan public

Before the service can be provisioned by apps, it must be made public. This can be done using the update-service-plan command with the stackato client.

C:\Users\watrous\Documents\echogo>stackato update-service-plan large --vendor "Echo Service" --public --free
Updating service plan [large.Echo Service] ...
Name (large):
Description (A large dedicated service with a big storage quota, lots of RAM, and many connections):
  Changed public      to "1"
  Changed free        to "1"
OK

From the command line, it is possible to list available services with the marketplace command. This shows the Echo Service that was just made public.

C:\Users\watrous\Documents\GitHub\cf-service-broker-python>stackato marketplace
https://api.stackato.danielwatrous.com -> Test -> somespace
+--------------+-------+-------------------------------+---------------------------------------------------------------------------------------+------+--------+----------+---------+------+
| Vendor       | Plan  | Description                   | Details                                                                               | Free | Public | Provider | Version | Orgs |
+--------------+-------+-------------------------------+---------------------------------------------------------------------------------------+------+--------+----------+---------+------+
| filesystem   | free  | Persistent filesystem service | free                                                                                  | yes  | yes    | core     | 1.0     |      |
| mysql        | free  | MySQL database service        | free                                                                                  | yes  | yes    | core     | 5.5     |      |
| postgresql   | free  | PostgreSQL database service   | free                                                                                  | yes  | yes    | core     | 9.1     |      |
| Echo Service | large | Echo back the value received  | A large dedicated service with a big storage quota, lots of RAM, and many connections | yes  | yes    |          |         |      |
+--------------+-------+-------------------------------+---------------------------------------------------------------------------------------+------+--------+----------+---------+------+

The Stackato web interface also shows the services provided by the broker under Admin -> Services -> Available Services. Notice that this shows both public and non-public services.

stackato-service-broker-listing

Manage Services

With the new service in the marketplace, it’s now possible to provision, bind and use instances of the new service by way of the stackato client. The most simple way to accomplish this is to call create-service and Provide the name of the service. Calling stackato services shows all service plans (including non-public) and provisioned services.

C:\Users\watrous\Documents\GitHub\cf-service-broker-python>stackato create-service "Echo Service"
1. large: A large dedicated service with a big storage quota, lots of RAM, and many connections
Please select the service plan to enact: 1
Creating new service [Echo Service-399dc] ... OK
 
C:\Users\watrous\Documents\GitHub\cf-service-broker-python>stackato services
 
============== Service Plans ================
 
+----------------+-------+-------------------------------+---------------------------------------------------------------------------------------+------+--------+----------+---------+------+
| Vendor         | Plan  | Description                   | Details                                                                               | Free | Public | Provider | Version | Orgs |
+----------------+-------+-------------------------------+---------------------------------------------------------------------------------------+------+--------+----------+---------+------+
| filesystem     | free  | Persistent filesystem service | free                                                                                  | yes  | yes    | core     | 1.0     |      |
| mysql          | free  | MySQL database service        | free                                                                                  | yes  | yes    | core     | 5.5     |      |
| postgresql     | free  | PostgreSQL database service   | free                                                                                  | yes  | yes    | core     | 9.1     |      |
| Echo Service   | large | Echo back the value received  | A large dedicated service with a big storage quota, lots of RAM, and many connections | yes  | yes    |          |         |      |
| Invert Service | small | Invert the value received     | A small shared service with a small storage quota and few connections                 | yes  | no     |          |         |      |
+----------------+-------+-------------------------------+---------------------------------------------------------------------------------------+------+--------+----------+---------+------+
 
=========== Provisioned Services ============
 
+-----------------+-----------------------+--------------+----------+---------+-------+----------------+
| Space           | Name                  | Service      | Provider | Version | Plan  | Applications   |
+-----------------+-----------------------+--------------+----------+---------+-------+----------------+
| Test::somespace | Echo Service-399dc    | Echo Service |          |         | large |                |
+-----------------+-----------------------+--------------+----------+---------+-------+----------------+

Push an app

It is now possible to bind apps to services. There are two ways to do this. The first is to provision a new instance of the service when the app is pushed. The other is to bind an existing service to an app when it is pushed. Both options come as prompts during the push process.

The service details will be injected into the app environment in a JSON document under the key VCAP_SERVICES. The structure of the service details is shown below.

{
  "Echo Service": [
    {
      "name": "Echo Service-399dc",
      "label": "Echo Service",
      "tags": [
 
      ],
      "plan": "large",
      "credentials": {
        "uri": "http://echo-service.stackato.danielwatrous.com/echo/db5e119a-823b-4368-a8d6-ff5f0cc90cb9/85629bfa-736b-42fd-9554-3f5df0bea097"
      }
    }
  ]
}

For this example, a simple Go script illustrates how to interact with the environment to extract the service connection details, then to make a call to the service.

package main
 
import (
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "strings"
    "encoding/json"
    "io"
)
 
func get_echo_uri() string {
    vcap_services := os.Getenv("VCAP_SERVICES")
    log.Print(vcap_services)
    decoder := json.NewDecoder(strings.NewReader(vcap_services))
    type Service struct {
        Echo_Service []struct {
            Credentials struct {
                Uri string `json:"uri"`
            } `json:"credentials"`
            Label string        `json:"label"`
            Name  string        `json:"name"`
            Plan  string        `json:"plan"`
            Tags  []interface{} `json:"tags"`
        } `json:"Echo Service"`
    }
    var service Service
    for {
        if err := decoder.Decode(&service); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
    }
    return service.Echo_Service[0].Credentials.Uri
}
 
func vcap_json_decode(w http.ResponseWriter, r *http.Request) {
    uri := get_echo_uri()
    fmt.Fprintf(w, "URI: %s\n", uri)
}
 
func environment(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "<a href=\"/decode\">decode</a>\n")
    fmt.Fprintf(w, "<a href=\"/echo\">echo</a>\n")
    for _, e := range os.Environ() {
        pair := strings.Split(e, "=")
        fmt.Fprintf(w, "%s = %s\n", pair[0], pair[1])
    }
}
 
func echo(w http.ResponseWriter, r *http.Request) {
    uri := get_echo_uri()
    var message = []byte(`{"message": "Hello Go!"}`)
    req, err := http.NewRequest("POST", uri, bytes.NewBuffer(message))
    req.Header.Set("Content-Type", "application/json")
 
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
 
    fmt.Fprintf(w, "response Status: %s\n", resp.Status)
    fmt.Fprintf(w, "response Headers: %s\n", resp.Header)
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Fprintf(w, "response Body: %s\n", string(body))
}
 
func main() {
    http.HandleFunc("/", environment)
    http.HandleFunc("/decode", vcap_json_decode)
    http.HandleFunc("/echo", echo)
    addr := ":" + os.Getenv("PORT")
	fmt.Printf("Listening on %v\n", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}

Additional requirements

In order to push this script to Stackato, a few additional files are required: Procfile, manifest.yml and .godir. The Procfile tells Stackato which command to use to start the application. The manifest indicates the Go version and application name. The .godir file contains a path (a name really) that will be used to create the binary version of the application during the staging process.

Download all echo application files as a zip

When the app is pushed, the service is created, bound and injected directly.

C:\Users\watrous\Documents\echogo>stackato push
Would you like to deploy from the current directory ?  [Yn]:
Using manifest file "manifest.yml"
Application Deployed URL [go-environment.stackato.danielwatrous.com]:
Application Url:   https://go-environment.stackato.danielwatrous.com
Enter Disk Reservation [2048]:
Enter GOVERSION [1.2]:
  Adding Environment Variable [GOVERSION=1.2]
Creating Application [go-environment] as [https://api.stackato.danielwatrous.com -> Test -> somespace -> go-environment] ... OK
  Map https://go-environment.stackato.danielwatrous.com ... OK
Bind existing services to 'go-environment' ?  [yN]:
Create services to bind to 'go-environment' ?  [yN]: y
What kind of service ?
1. free.filesystem
2. free.mysql
3. free.postgresql
4. large.Echo Service
5. small.Invert Service
Choose: 4
Specify the name of the service [large.Echo Service-b8309]:
Creating new service [large.Echo Service-b8309] ... OK
Binding large.Echo Service-b8309 to go-environment ... OK
Create another ?  [yN]:
Uploading Application [go-environment] ...
  Checking for bad links ...  OK
  Copying to temp space ...  OK
  Checking for available resources ...  OK
  Processing resources ... OK
  Packing application ... OK
  Uploading (10K) ...  OK
Push Status: OK
Starting Application [go-environment] ...
OK
http://go-environment.stackato.danielwatrous.com/ deployed

The bound service instance is shown in the Services tab on the application page in the Stackato web interface.

services-listing

The new application can be loaded to confirm that the service is working and properly injected into the application environment.

echo-go-response

 

References

ASIDE: I found it unsatisfying to work with JSON in Go. Many languages, like PHP and Python, find clean ways of mapping JSON on to core data types, such as associative arrays or dictionaries. The need to define structs in Go to accommodate the anticipated JSON document felt like a throwback to XML and DTDs. Here are some references and it’s possible there’s a more elegant or sophisticated way to accomplish what I’ve done. If so, leave it in the comments below.

http://mervine.net/json2struct
http://golang.org/pkg/encoding/json/
http://blog.golang.org/json-and-go
https://github.com/ChimeraCoder/gojson

Software Engineering

User provided services in CloudFoundry

This post builds on the discussion of Managed Services in CloudFoundry and covers the first of the two methods for using unmanaged services in CloudFoundry. It makes use of the Python echo service and Python service broker API implementation used previously.

Manually provision the service

This method assumes that an existing service has been provisioned outside of CloudFoundry. An instance of the echo service can be manually provisioned using cURL using the following commands. In this example, a new instance is provisioned with the id user-service-instance and bound to an app with id someapp. The new instance and binding is then confirmed to work by echoing a message.

vagrant@vagrant-ubuntu-trusty-64:~$ curl -X PUT http://echo-service.10.244.0.34.xip.io/echo/user-service-instance -w "\n"
{"instance_id": "user-service-instance", "state": "provision_success", "dashboard_url": "http://localhost:8090/dashboard/user-service-instance"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X PUT http://echo-service.10.244.0.34.xip.io/echo/user-service-instance/someapp -w "\n"
{"state": "bind_success", "id": "user-service-instance", "app": "someapp"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X POST http://echo-service.10.244.0.34.xip.io/echo/user-service-instance/someapp -H "Content-Type: application/json" -d '{"message": "Hello Test"}' -w "\n"
{"response": "Hello Test"}

The URI that should be provided to an application is

http://echo-service.10.244.0.34.xip.io/echo/user-service-instance/someapp

Add the service to CloudFoundry

The service can now be manually added to CloudFoundry as a user provided service. The CloudFoundry documentation suggests a predefined structure for the service parameters, but freeform JSON can be provided.

vagrant@vagrant-ubuntu-trusty-64:~$ cf cups user-echo-service -p '{"uri": "http://echo-service.10.244.0.34.xip.io/echo/user-service-instance/someapp"}'
Creating user provided service user-echo-service in org myorg / space mydept as admin...
OK
vagrant@vagrant-ubuntu-trusty-64:~$ cf services
Getting services in org myorg / space mydept as admin...
OK
 
name                service         plan    bound apps
test-echo-service   Echo Service    large   echo-php
user-echo-service   user-provided

The service is now available and can be bound to any existing app. Binding the user-provided service to an app will cause CloudFoundry to inject service details into each app instance. The next command assumes the existence of an app echo-php. The app must be restaged to enable environment injection.

vagrant@vagrant-ubuntu-trusty-64:~$ cf bind-service echo-php user-echo-service
Binding service user-echo-service to app echo-php in org myorg / space mydept as admin...
OK
TIP: Use 'cf restage' to ensure your env variable changes take effect
vagrant@vagrant-ubuntu-trusty-64:~$ cf restage echo-php
Restaging app echo-php in org myorg / space mydept as admin...
-----> Downloaded app package (4.0K)
-----> Downloaded app buildpack cache (4.0K)
-------> Buildpack version 1.0.2
Use locally cached dependencies where possible
 !     WARNING:        No composer.json found.
       Using index.php to declare PHP applications is considered legacy
       functionality and may lead to unexpected behavior.
       See https://devcenter.heroku.com/categories/php
-----> Setting up runtime environment...
       - PHP 5.5.12
       - Apache 2.4.9
       - Nginx 1.4.6
-----> Installing PHP extensions:
       - opcache (automatic; bundled, using 'ext-opcache.ini')
-----> Installing dependencies...
       Composer version ac497feabaa0d247c441178b7b4aaa4c61b07399 2014-06-10 14:13:12
       Warning: This development build of composer is over 30 days old. It is recommended to update it by running "/app/.heroku/php/bin/composer self-update" to get the latest version.
       Loading composer repositories with package information
       Installing dependencies
       Nothing to install or update
       Generating optimized autoload files
-----> Building runtime environment...
       NOTICE: No Procfile, defaulting to 'web: vendor/bin/heroku-php-apache2'
 
-----> Uploading droplet (64M)
 
0 of 1 instances running, 1 starting
1 of 1 instances running
 
App started
 
 
OK
Showing health and status for app echo-php in org myorg / space mydept as admin...
OK
 
requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: echo-php.10.244.0.34.xip.io
last uploaded: Mon Nov 24 21:44:40 UTC 2014
 
     state     since                    cpu    memory          disk
#0   running   2014-11-25 05:31:22 PM   0.0%   87.3M of 256M   0 of 1G

The env command can then be used to confirm that the user-provided service is available in the environment for the app.

vagrant@vagrant-ubuntu-trusty-64:~$ cf env echo-php
Getting env variables for app echo-php in org myorg / space mydept as admin...
OK
 
System-Provided:
{
 "VCAP_SERVICES": {
  "Echo Service": [
   {
    "credentials": {
     "uri": "http://echo-service.10.244.0.34.xip.io/echo/8dea3b8e-4230-4509-8879-5f94b4812985/d2944b66-2d9e-46fe-8e3d-d9b102cb5bca"
    },
    "label": "Echo Service",
    "name": "test-echo-service",
    "plan": "large",
    "tags": []
   }
  ],
  "user-provided": [
   {
    "credentials": {
     "uri": "http://echo-service.10.244.0.34.xip.io/echo/user-service-instance/someapp"
    },
    "label": "user-provided",
    "name": "user-echo-service",
    "syslog_drain_url": "",
    "tags": []
   }
  ]
 }
}
 
{
 "VCAP_APPLICATION": {
  "application_name": "echo-php",
  "application_uris": [
   "echo-php.10.244.0.34.xip.io"
  ],
  "application_version": "921b2927-cedd-4623-9db5-b0f48985de39",
  "limits": {
   "disk": 1024,
   "fds": 16384,
   "mem": 256
  },
  "name": "echo-php",
  "space_id": "275cf0c6-b10e-4ef1-afbc-966880feb09f",
  "space_name": "mydept",
  "uris": [
   "echo-php.10.244.0.34.xip.io"
  ],
  "users": null,
  "version": "921b2927-cedd-4623-9db5-b0f48985de39"
 }
}
 
No user-defined env variables have been set
 
No running env variables have been set
 
No staging env variables have been set

From the output of the environment it is seen that this app has two service bindings. The first binding corresponds to a managed instance of the echo service. The second is the service instance that was manually provisioned in this post. It’s up to the app to extract and use the appropriate service. This means that the app must specifically look for the service connection values that are injected.

Software Engineering

Managed Services in CloudFoundry

CloudFoundry defines a Service Broker API which can be implemented and added to a CloudFoundry installation to provide managed services for apps. In order to better understand the way managed services are created and integrated with CloudFoundry (and derivative technologies like Stackato and HP Helion Development Platform), I created an example service and implemented the Service Broker API to manage it. The code for both implementations are on github.

Deploy the services

For this exercise I deployed bosh-lite on my Windows 7 laptop. I then follow the documented procedure to push cf-echo-service and cf-service-broker-python to CloudFoundry. The desired end state is two running apps in CloudFoundry that can be used for service broker management and testing.

NOTE: Deploy the echo-service first. Then update the service-broker script so that it will use the cf hosted echo-service. The relevant lines in the service-broker.py script are shown.

# UPDATE THIS FOR YOUR ECHO SERVICE DEPLOYMENT
service_base = "echo-service.10.244.0.34.xip.io"

Security

In this setup, it is necessary for the service-broker app to communicate with the echo-service app within CloudFoundry. This requires adding a security group and applying it to running services. The first step is to create a JSON file with the definition of the new security group.

[
  {
    "protocol": "tcp",
    "destination": "10.244.0.34",
    "ports": "80"
  }
]

The security group can then be created using create-security-group. This command expects a security group name and a path to the JSON file created above. After creating the security group, it must be bound to running instances (optionally to staging instances too using bind-staging-security-group). If the service-broker was deployed prior to enabling this security group, the app will also need to be restarted.

vagrant@vagrant-ubuntu-trusty-64:~$ cf create-security-group port80 security.json
Creating security group port80 as admin
OK
vagrant@vagrant-ubuntu-trusty-64:~$ cf security-groups
Getting security groups as admin
OK
 
     Name              Organization   Space
#0   public_networks
#1   dns
#2   port80
vagrant@vagrant-ubuntu-trusty-64:~$ cf bind-running-security-group port80
Binding security group port80 to defaults for running as admin
OK
 
TIP: Changes will not apply to existing running applications until they are restarted.
vagrant@vagrant-ubuntu-trusty-64:~$ cf restart service-broker
Stopping app service-broker in org myorg / space mydept as admin...
OK
 
Starting app service-broker in org myorg / space mydept as admin...
 
0 of 1 instances running, 1 starting
1 of 1 instances running
 
App started
 
OK
Showing health and status for app service-broker in org myorg / space mydept as admin...
OK
 
requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: service-broker.10.244.0.34.xip.io
last uploaded: Mon Nov 24 15:59:07 UTC 2014
 
     state     since                    cpu    memory        disk
#0   running   2014-11-24 08:13:40 PM   0.0%   56M of 256M   0 of 1G

NOTE: I deploy them to CloudFoundry for convenience and reliability, but this is not required. The service could be any service and the service broker API implementation can be deployed anywhere.

vagrant@vagrant-ubuntu-trusty-64:~$ cf apps
Getting apps in org myorg / space mydept as admin...
OK
 
name             requested state   instances   memory   disk   urls
echo-service     started           1/1         256M     1G     echo-service.10.244.0.34.xip.io
service-broker   started           1/1         256M     1G     service-broker.10.244.0.34.xip.io

Optional validation

It is possible to validate that the deployed apps work as expected. The curl commands below validate the echo service is working properly.

vagrant@vagrant-ubuntu-trusty-64:~$ curl -X PUT http://echo-service.10.244.0.34.xip.io/echo/51897770-560b-11e4-b75a-9ad2017223a9 -w "\n"
{"instance_id": "51897770-560b-11e4-b75a-9ad2017223a9", "state": "provision_success", "dashboard_url": "http://localhost:8090/dashboard/51897770-560b-11e4-b75a-9ad2017223a9"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X PUT http://echo-service.10.244.0.34.xip.io/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -w "\n"
{"state": "bind_success", "id": "51897770-560b-11e4-b75a-9ad2017223a9", "app": "myapp"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X POST http://echo-service.10.244.0.34.xip.io/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -H "Content-Type: application/json" -d '{"message": "Hello World"}' -w "\n"
{"response": "Hello World"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X POST http://echo-service.10.244.0.34.xip.io/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -H "Content-Type: application/json" -d '{"message": "Hello World 2"}' -w "\n"
{"response": "Hello World 2"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X POST http://echo-service.10.244.0.34.xip.io/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -H "Content-Type: application/json" -d '{"message": "Hello Worldz!"}' -w "\n"
{"response": "Hello Worldz!"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X GET http://echo-service.10.244.0.34.xip.io/echo/dashboard/51897770-560b-11e4-b75a-9ad2017223a9
 
<table class="pure-table">
    <thead>
        <tr>
            <th>Instance</th>
            <th>Bindings</th>
            <th>Messages</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>51897770-560b-11e4-b75a-9ad2017223a9</td>
            <td>1</td>
            <td>3</td>
        </tr>
    </tbody>
</table>
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X DELETE http://echo-service.10.244.0.34.xip.io/echo/51897770-560b-11e4-b75a-9ad2017223a9/myapp -w "\n"
{"state": "unbind_success", "id": "51897770-560b-11e4-b75a-9ad2017223a9", "app": "myapp"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X DELETE http://echo-service.10.244.0.34.xip.io/echo/51897770-560b-11e4-b75a-9ad2017223a9 -w "\n"
{"state": "deprovision_success", "bindings": 0, "id": "51897770-560b-11e4-b75a-9ad2017223a9", "messages": 3}

The service broker API implementation can also be verified using curl as shown below.

vagrant@vagrant-ubuntu-trusty-64:~$ curl -X GET http://service-broker.10.244.0.34.xip.io/v2/catalog -H "X-Broker-Api-Version: 2.3" -H "Authorization: Basic dXNlcjpwYXNz" -w "\n"
{"services": [{"name": "Echo Service", "dashboard_client": {"id": "client-id-1", "redirect_uri": "http://echo-service.10.244.0.34.xip.io/echo/dashboard", "secret": "secret-1"}, "description": "Echo back the value received", "id": "echo_service", "plans": [{"free": false, "description": "A large dedicated service with a big storage quota, lots of RAM, and many connections", "id": "big_0001", "name": "large"}], "bindable": true}, {"name": "Invert Service", "dashboard_client": null, "description": "Invert the value received", "id": "invert_service", "plans": [{"description": "A small shared service with a small storage quota and few connections", "id": "small_0001", "name": "small"}], "bindable": true}]}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X PUT http://service-broker.10.244.0.34.xip.io/v2/service_instances/mynewinstance -H "Content-type: application/json" -H "Authorization: Basic dXNlcjpwYXNz" -d '{"service_id": "echo_service", "plan_id": "small_0001", "organization_guid": "HP", "space_guid": "IT"}' -w "\n"
{"dashboard_url": "http://echo-service.10.244.0.34.xip.io/echo/dashboard/mynewinstance"}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X PUT http://service-broker.10.244.0.34.xip.io/v2/service_instances/mynewinstance/service_bindings/myappid  -H "Content-type: application/json" -H "Authorization: Basic dXNlcjpwYXNz" -d '{"service_id": "echo_service", "plan_id": "small_0001", "app_guid": "otherappid"}' -w "\n"
{"credentials": {"uri": "http://echo-service.10.244.0.34.xip.io/echo/mynewinstance/myappid"}}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X DELETE http://service-broker.10.244.0.34.xip.io/v2/service_instances/mynewinstance/service_bindings/myappid  -H "Authorization: Basic dXNlcjpwYXNz" -w "\n"
{}
vagrant@vagrant-ubuntu-trusty-64:~$ curl -X DELETE http://service-broker.10.244.0.34.xip.io/v2/service_instances/mynewinstance -H "Authorization: Basic dXNlcjpwYXNz" -w "\n"
{}

Manage the Service Broker

In CloudFoundry, the service broker can be added using the create-service-broker command with the cf cli.

vagrant@vagrant-ubuntu-trusty-64:~$ cf create-service-broker echo-broker user pass http://service-broker.10.244.0.34.xip.io
Creating service broker echo-broker as admin...
OK
vagrant@vagrant-ubuntu-trusty-64:~$ cf service-brokers
Getting service brokers as admin...
 
name          url
echo-broker   http://service-broker.10.244.0.34.xip.io

The service broker is added, but the service plans in the catalog do not allow public access by default, as can be seen with the call to service-access below. This also means that calls to marketplace show no available services.

vagrant@vagrant-ubuntu-trusty-64:~$ cf service-access
Getting service access as admin...
broker: echo-broker
   service          plan    access   orgs
   Echo Service     large   none
   Invert Service   small   none
vagrant@vagrant-ubuntu-trusty-64:~$ cf marketplace
Getting services from marketplace in org myorg / space mydept as admin...
OK
 
No service offerings found

The the service plan must be enabled to accommodate public consumption. This is done using the enable-service-access call and providing the name of the service. In this case, the “Echo Service” is desired. After enabling the service plan, the service-access listing shows it available and a call to marketplace shows it available to apps.

vagrant@vagrant-ubuntu-trusty-64:~$ cf enable-service-access "Echo Service"
Enabling access to all plans of service Echo Service for all orgs as admin...
OK
vagrant@vagrant-ubuntu-trusty-64:~$ cf service-access
Getting service access as admin...
broker: echo-broker
   service          plan    access   orgs
   Echo Service     large   all
   Invert Service   small   none
 
vagrant@vagrant-ubuntu-trusty-64:~$ cf marketplace
Getting services from marketplace in org myorg / space mydept as admin...
OK
 
service        plans   description
Echo Service   large   Echo back the value received

Manage Services

With the new service in the marketplace, it’s now possible to provision, bind and use instances of the new service by way of the cf cli. This happens in a few steps in CloudFoundry. First an instance of the service is provisioned. An app is then pushed to CloudFoundry. Finally the service and the app are associated through a binding. The call to create-service expects three arguments which can be found in the marketplace output above.

  • service name
  • service plan
  • a name for the new service instance
vagrant@vagrant-ubuntu-trusty-64:~$ cf create-service "Echo Service" large test-echo-service
Creating service test-echo-service in org myorg / space mydept as admin...
OK
vagrant@vagrant-ubuntu-trusty-64:~$ cf services
Getting services in org myorg / space mydept as admin...
OK
 
name                service        plan    bound apps
test-echo-service   Echo Service   large

Push an app

Next step is to create a simple app to which the service can be bound. It’s important to understand how the service details will be injected into the app environment. Service details will be contained in a JSON document under the key VCAP_SERVICES. The structure of the service details is shown below.

{
  "Echo Service": [
    {
      "name": "test-php-echo",
      "label": "Echo Service",
      "tags": [
 
      ],
      "plan": "large",
      "credentials": {
        "uri": "http://16.98.49.183:8090/echo/f2c43d9c-e912-4e56-b624-14e8522be912/4a421367-0e1a-4b56-a07f-9a6b404119a5"
      }
    }
  ]
}

For this example, the PHP script below extracts, decodes and isolates the service URI from the VCAP_SERVICES environment variable. The cURL library is used to send a call to that URI containing a basic message JSON document. The response is then printed using var_dump.

<?php
// extract injected service details from environment
$services_json = getenv("VCAP_SERVICES");
$services = json_decode($services_json);
$service_uri = $services->{'Echo Service'}[0]->{'credentials'}->{'uri'};
var_dump($service_uri);
 
// create a message to send
$message = '{"message": "Hello CloudFoundry!"}';
 
// setup and execute the cURL call to the echo service
$curl = curl_init($service_uri);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $message);
$response = curl_exec($curl);
 
// handle non-200 response
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ( $status != 200 ) {
    die("Error: call to URL $url failed with status $status, response $response, curl_error " . curl_error($curl) . ", curl_errno " . curl_errno($curl));
}
 
// clean up cURL connection
curl_close($curl);
 
// decode response and output
$response_as_json = json_decode($response);
var_dump($response_as_json);
?>

A simple manifest file is provided for the deployment.

applications:
- name: echo-php
  framework: php

The app can then be pushed to CloudFoundry. The resulting list of apps is shown.

vagrant@vagrant-ubuntu-trusty-64:~/echo-php$ cf apps
Getting apps in org myorg / space mydept as admin...
OK
 
name             requested state   instances   memory   disk   urls
echo-service     started           1/1         256M     1G     echo-service.10.244.0.34.xip.io, 16.85.146.179.xip.io
service-broker   started           1/1         256M     1G     service-broker.10.244.0.34.xip.io
echo-php         started           1/1         256M     1G     echo-php.10.244.0.34.xip.io

Bind the service to the app

With an existing service and an existing app, it’s possible to bind the service to the app. This is done using bind-service, as shown below. Note that it is necessary to restage the app after binding the service so that the environment variables will be properly injected.

vagrant@vagrant-ubuntu-trusty-64:~$ cf bind-service echo-php test-echo-service
Binding service test-echo-service to app echo-php in org myorg / space mydept as admin...
OK
TIP: Use 'cf restage' to ensure your env variable changes take effect
vagrant@vagrant-ubuntu-trusty-64:~$ cf restage echo-php
Restaging app echo-php in org myorg / space mydept as admin...
-----> Downloaded app package (4.0K)
-----> Downloaded app buildpack cache (4.0K)
-------> Buildpack version 1.0.2
Use locally cached dependencies where possible
 !     WARNING:        No composer.json found.
       Using index.php to declare PHP applications is considered legacy
       functionality and may lead to unexpected behavior.
       See https://devcenter.heroku.com/categories/php
-----> Setting up runtime environment...
       - PHP 5.5.12
       - Apache 2.4.9
       - Nginx 1.4.6
-----> Installing PHP extensions:
       - opcache (automatic; bundled, using 'ext-opcache.ini')
-----> Installing dependencies...
       Composer version ac497feabaa0d247c441178b7b4aaa4c61b07399 2014-06-10 14:13:12
       Warning: This development build of composer is over 30 days old. It is recommended to update it by running "/app/.heroku/php/bin/composer self-update" to get the latest version.
       Loading composer repositories with package information
       Installing dependencies
       Nothing to install or update
       Generating optimized autoload files
-----> Building runtime environment...
       NOTICE: No Procfile, defaulting to 'web: vendor/bin/heroku-php-apache2'
 
-----> Uploading droplet (64M)
 
0 of 1 instances running, 1 starting
1 of 1 instances running
 
App started
 
 
OK
Showing health and status for app echo-php in org myorg / space mydept as admin...
OK
 
requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: echo-php.10.244.0.34.xip.io
last uploaded: Mon Nov 24 21:34:29 UTC 2014
 
     state     since                    cpu    memory          disk
#0   running   2014-11-24 09:39:27 PM   0.0%   88.4M of 256M   0 of 1G

The echo-php service can be called using cURL from the command line to verify proper connectivity between the echo php app and the new service.

vagrant@vagrant-ubuntu-trusty-64:$ curl http://echo-php.10.244.0.34.xip.io
string(117) "http://echo-service.10.244.0.34.xip.io/echo/8dea3b8e-4230-4509-8879-5f94b4812985/d2944b66-2d9e-46fe-8e3d-d9b102cb5bca"
object(stdClass)#4 (1) {
  ["response"]=>
  string(19) "Hello CloudFoundry!"
}

The existing service can be bound to additional app instances. It is also trivial to create new service instances. If the service is no longer needed by the application, it is straightforward to unbind and deprovision.

Software Engineering

External Services in CloudFoundry

CloudFoundry, Stackato and Helion Development Platform accommodate (and encourage) external services for persistent application needs. The types of services include relational databases, like MySQL or PostgreSQL, NoSQL datastores, like MongoDB, messaging services like RabbitMQ and even cache technologies like Redis and Memcached. In each case, connection details, such as a URL, PORT and credentials, are maintained by the cloud controller and injected into the environment of new application instances.

cloudfoundry-service-injection

Injection

It’s important to understand that regardless of how the cloud controller receives details about the service, the process of getting those details to application instances is the same. Much like applications use dependency injection, the cloud controller injects environment variables into each application instance. The application is written to use these environment variables to establish a connection to the external resource. From an application perspective, this looks the same whether a warden or docker container is used.

Connecting to the Service

Connecting to the service from the application instance is the responsibility of the application. There is nothing in CloudFoundry or its derivatives that facilitates this connection beyond injecting the connection parameters into the application instance container. The fact that there is no intermediary between the application instance and the service means that there is no additional latency or potential disconnect. However, the fact that CloudFoundry can scale to an indefinite number of application instances does mean the external service must be able to accommodate all the connections that will result.

Connection pooling is a popular method to reduce the overhead of creating new connections. Since CloudFoundry scales out to many instances, it may be less valuable to manage connection pooling in the application. This may increase memory usage on the application instance while consuming available connections that should be distributed among all instances.

Managed vs. Unmanaged

The Service Broker API may be implemented to facilitate provisioning, binding, unbinding and deprovisioning of resources. This is referred to as a managed service, since the life-cycle of the resource is managed by the PaaS. In the case of managed services, the user interacts with the service only by way of the CloudFoundry command line client.

In an unmanaged scenario, the service resource is provisioned outside of the PaaS. The user then provides connection details to the PaaS in one of two ways.

  • The first is to register it as a service that can then be bound to application instances.
  • The second is to add connection details manually as individual environment variable key/name pairs.

The three methods of incorporating services discussed in this post range from high to low touch and make it possible to incorporate any type of service, even existing services.

Use caution when designing services to prevent them from getting overwhelmed. The scalable character of CloudFoundry means that the number of instances making connections to a service can grow very quickly to an indeterminate number.