Golang dev with Kubernetes, Helm and Skaffold

I’ve really been feeling the love for Go recently. I love its simplicity. I love its superb developer experience. I love how my tests run faster and my microservices spin up faster then I can blink! I love that it has so many utilities that I need for creating microservices built in to its standard library. Oh and did I mention that its explicit error handling is a joy – so much nicer then being blind-sided with the exception throwing so common in many other languages. It’s just a truly wonderful language and ecosystem to work with especially when it comes to backend services.

In a previous post, I spoke about how Kubernetes simplifies the tasks of deploying and managing microservices in the cloud. Managing a whole suite of microservices together with the infrastructural elements they depend on e.g. message queues and databases can lead to quite a lot of Kubernetes specifications though. It’s a lot of yaml to be managing! Also, how do we manage all of this across all our different environments? This is where Helm comes in. It looks after parameterising and packaging all the Kubernetes specifications needed to deploy and manage all the elements that make up the system that we want to deliver.

The great thing about using Helm and Kubernetes is that we can replicate the prod environment that we are deploying to on our local machines. A Kubernetes cluster can be created with ease locally with tools such as Minikube, Rancher Desktop and others. We can deploy to our local Kubernetes environment with Helm in the same way we deploy to cloud based environments!

But wait! If everything is running in our local Kubernetes cluster, isn’t that a total pain constantly having to spin up pods or get our source code changes compiled into running pods? Nope! Skaffold does this for us and more!

courses-service

To put all of this into action, we’re going to imagine that we are building a small part of an e-learning platform. We are going to build the courses-service. This is a microservice that handles data related to the courses that are offered on the platform. We’re going to keep this pretty simple. All the microservice is going to do for now is offer an API to create a new course and to retrieve all existing courses. It will store these courses as records in a table in a Postgres DB.

Getting set up

To follow along, you will need Go installed . You also need a local Kubernetes cluster. Rancher Desktop does the job here and it also includes helm and kubectl ,which is a command line tool for interacting with our Kubernetes cluster. These can be enabled in “Supporting Utilities”. If you get a permissions error here though, no worries, we can always just add them to the PATH.

On M1 MacOS, these utilities are located here:

/Applications/Rancher\ Desktop.app/Contents/Resources/resources/darwin/bin

This can be added to your PATH in .zshrc or .bashrc etc. (depending on which shell you use)

export PATH=/Applications/Rancher\ Desktop.app/Contents/Resources/resources/darwin/bin:$PATH

IDE

Goland, like all Jetbrains tools, is an amazing IDE for Go development. An alternative, which is free, is to use VSCode with the Go plugin. I use Goland as I’ve been using Jetbrains IDEs for years so they are pretty embedded in my muscle memory but VSCode is great too!

Lets get coding!

We can create our courses-service as a Go module by creating a directory and running a go command to initialize a new module as shown below. As this e-learning platform is all about exchanging know-how, let’s call our module path “swapknowhow/courses”.

mkdir -p swapknowhow/courses
cd swapknowhow/courses
go mod init swapknowhow/courses

This microservice is going to store course information as records in a postgres db. However, its too soon to start thinking about the specific db technology that we are going to use. Let’s just concentrate on building out the core use cases. We have 2 use cases. We want to be able to create a new course and we want to be able to get back all the courses that have been created.

This is a very thin microservice with little or no logic. In this case, it can be simply tested at the Api level. If this were something like Java Spring Boot, I would shy away from this because it would involve spinning up a local server to run my tests which is slow! In Go there is a package in the standard library for testing at the api level without needing to even spin up a server. Instances of Go ResponseWriter and Request can easily be created and passed to the handler functions that we want to test.

I use TDD whenever I feel it makes sense for what I’m working on. I used it in building out this microservice. This process is very iterative. I write enough of a test that is sufficient to fail and then write the production code to make it pass followed by some refactoring if needed. A lot of the time, the test I write that is sufficient to fail only fails because it can’t compile – usually because I haven’t created the core production code types and functions it depends on. So, in this case, creating these is enough to make the current test pass. It would be very unwieldy for the reader for me to go through every step of that iterative process in showing how I built out this microservice. So I will, instead, show the final api test that I ended up with and explain the main parts. Then I will show the core production code that makes this all pass. This core code doesn’t know anything about a postgres db. It just knows that we will use a repository with a defined method set. Once the api tests and core production code are written, hooking in an implementation of the repostiory that uses a postgres db, is pretty quick. Ideally, there would also be an integration test to test the full end-to-end of making requests to the microservice and the microservice using postgres. Perhaps that can be the subject of a future blog post 🙂

So, with all that being said lets dive in. The real purpose of this blog post is to show how we can use Kubernetes, Helm and Skaffold to build and package our service. So I won’t dwell on the core Go code too much. The full code along with helm and skaffold configurations is available on my Github.

The code for the API handlers is going to be in a directory called api. The api test code will go in here too. So the directory structure is as follows.

Our api tests are in courses_api_test.go. Since there are just two use cases – create a course and get all courses, this can really be tested in one test function – for the happy path at least.

This test is in the function below:

func TestCanCreateAndRetrieveCourses(t *testing.T) {
	api := Api{CoursesRepo: newInMemoryCoursesRepositoryStub()}

	courseToCreate := courses.Course{
		Name:           "test course",
		Rating:         5,
		Descripton:     "course to test",
		DurationMillis: 50,
	}
	jsonBody, _ := json.Marshal(courseToCreate)
	body := strings.NewReader(string(jsonBody))
	courseCreationRequest := httptest.NewRequest("POST", "/courses", body)
	courseCreationResponseRecorder := httptest.NewRecorder()

	api.Courses(courseCreationResponseRecorder, courseCreationRequest)
	courseCreationResponse := courseCreationResponseRecorder.Result()
	defer courseCreationResponse.Body.Close()

	if courseCreationResponse.StatusCode != 201 {
		t.Errorf("expected course courseCreationResponse status: %v actual: %v", 201, courseCreationResponse.StatusCode)
	}

	req := httptest.NewRequest("GET", "/courses", nil)
	recorder := httptest.NewRecorder()

	api.Courses(recorder, req)

	res := recorder.Result()
	defer res.Body.Close()
	bytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		t.Errorf("error reading from http response writer, %v", err)
	}
	var coursesResponse []courses.Course
	json.Unmarshal(bytes, &coursesResponse)
	if len(coursesResponse) != 1 {
		t.Errorf("Expected coursesResponse length :%v, got %v", 1, len(coursesResponse))
	}

	course := (coursesResponse)[0]
	if course != courseToCreate {
		t.Errorf("Expected :%v, got %v", courseToCreate, course)
	}
}

The main thing to see here is that we exercise our endpoints by calling a method on our Api struct type (I will show this in a bit). This method handles GET and POST requests and delegates to its own private handlers. So, we call the endpoint to create a course here

courseCreationRequest := httptest.NewRequest("POST", "/courses", body)
	courseCreationResponseRecorder := httptest.NewRecorder()

	api.Courses(courseCreationResponseRecorder, courseCreationRequest)

and we call the endpoint to get all created courses here:

api.Courses(recorder, req)

	res := recorder.Result()
	defer res.Body.Close()
	bytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		t.Errorf("error reading from http response writer, %v", err)
	}
	var coursesResponse []courses.Course
	json.Unmarshal(bytes, &coursesResponse)

The rest of this test function is all about setting up test data – i.e. a Course (instance of a struct that I will show in a little bit) and asserting responses from our endpoints. I think to explain things further, we need to get into the production code.

In Go, the code that you want to remain private to your module goes inside a directory called “internal”. Our core types can all go in here in one Go file called courses.go in a Go package called “courses”. The directory structure is shown below. You can ignore the build and db directories for now.

The contents of the courses.go file is shown below

package courses

import (
	"github.com/gofrs/uuid"
	"time"
)

type Course struct {
	Uuid           uuid.UUID
	Created        time.Time
	Name           string
	Rating         int
	Descripton     string
	DurationMillis int
}

type CoursesRepository interface {
	GetCourses() []Course
	CreateCourse(course Course)
}

A course is represented as a struct called Course. It has a uuid to uniquely identify a course. The other fields are simple data fields that one would expect to find in a data type describing a course on most e-learning platforms – duration, rating etc.

We also defined the CoursesRepository interface. This is a method set that types need to implement in order to conform to this interface. In our case here, it’s simply a method to return a slice of Courses and one to create a course in the repository.

With that in place, the rest of our api test should make more sense. the first line of our test function contained

api := Api{CoursesRepo: newInMemoryCoursesRepositoryStub()}

Api is simply a struct with a method called Courses to expose our handler for GET and POST requests. It also has a field, CoursesRepo, to allow us to inject different implementations of our CoursesRepository interface. For the api test, we inject an in-memory repo where courses are simply saved to and retrieved from a slice. This in-memory implementation is also in our courses_api_test.go.

type CoursesRepositoryStub struct {
	courses []courses.Course
}

func (r *CoursesRepositoryStub) GetCourses() []courses.Course {
	return r.courses
}

func (r *CoursesRepositoryStub) CreateCourse(course courses.Course) {
	r.courses = append(r.courses, course)
}

func newInMemoryCoursesRepositoryStub() *CoursesRepositoryStub {
	return &CoursesRepositoryStub{courses: make([]courses.Course, 0, 10)}
}

So the only thing remaining to make our api test pass is to implement the Api struct along with its Courses method. This goes in a file called courses_api.go in the same directory as our courses_api_test.go. The contents of this (excluding package declaration and imports) is shown below:

type Api struct {
	CoursesRepo courses.CoursesRepository
}

func (api *Api) Courses(writer http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "GET":
		api.getCourses(writer, req)
	case "POST":
		api.createCourse(writer, req)
	default:
		writer.Write([]byte("Invalid method"))
		writer.WriteHeader(400)
	}
}

func (api *Api) createCourse(writer http.ResponseWriter, req *http.Request) {
	courseJson, err := ioutil.ReadAll(req.Body)
	if err != nil {
		fmt.Printf("error reading request body %v\n", err)
		writer.WriteHeader(500)
		return
	}
	var course courses.Course
	err = json.Unmarshal(courseJson, &course)
	if err != nil {
		fmt.Printf("error deserializing course %v \n", err)
		writer.WriteHeader(500)
		return
	}
	api.CoursesRepo.CreateCourse(course)
	writer.WriteHeader(201)
}

func (api *Api) getCourses(writer http.ResponseWriter, _ *http.Request) {
	coursesJson, err := json.Marshal(api.CoursesRepo.GetCourses())
	if err != nil {
		fmt.Printf("error marshalling courses %v", err)
		writer.WriteHeader(500)
	} else {
		_, err := writer.Write(coursesJson)
		if err != nil {
			fmt.Printf("error writing response %v", err)
		}
	}
}

There is actually only one public method here – the Courses method. In Go, capitalisation matters. A function is made public simply by having an uppercase character as the first character in its name as opposed to a lowercase character in the case of a private function. Our Courses method inspects the http method on the http request that it receives and delegates to a specific private method accordingly. These private methods, in turn, call out to the CoursesRepo on the Api struct to create a course or retrieve courses. The rest of the code is dealing with errors and using the Go standard library code for json marshalling and unmarshalling.

Adding Postgres

So for our courses-service microservice to work in the real world, we’ll need a proper data store. Postgres does the job here. So we need an implementation of our CoursesRepository interface that will call out to a postgres db. This is handled in another package within our internal directory. The package is simply called postgres and the code is in a Go file called postgres_repository.go with the directory structure shown below:

This uses a Go library called pgx which is available on Github. To retrieve this and add it as a dependency to our module, we can simple run the following from the root of our module.

go get github.com/jackc/pgx/v4

This library gives us a connection pool to connect to our db. As we will see shortly, we can run our postgres db locally via Kubernetes. We will set up the db to run on port 5432 and we will port-forward to the Pod it runs in to enable our courses-service microservice to connect to it. The dbname will simply be coursesdb. So our connection string will be as follows:

"user=postgres password=password host=localhost port=5432 dbname=coursesdb"

For now, we will just hard-code this connection string to point to our local postgres that will be running in a local Kubernetes Pod. Later, we will make this configurable. We can set this up with a struct and a function to create a new instance of the struct as follows:

type PostgresCoursesRepository struct {
	dbPool *pgxpool.Pool
	Close  func()
}

func NewPostgresCoursesRepository() *PostgresCoursesRepository {
	connection := "user=postgres password=password host=localhost port=5432 dbname=coursesdb"
	dbPool, err := pgxpool.Connect(context.Background(), connection)

	if err != nil {
		log.Fatal("db connection could not be established")
		os.Exit(1)
	}

	return &PostgresCoursesRepository{
		dbPool: dbPool,
		Close:  func() { dbPool.Close() },
	}
}

Now we need to give this struct a method set that conforms to our CoursesRepository interface and uses the pgx library to interact with postgres as follows:

func (repo *PostgresCoursesRepository) GetCourses() []courses.Course {
	rows, err := repo.dbPool.Query(context.Background(), "select * from courses;")
	if err != nil {
		fmt.Printf("Error querying courses in db: %v\n", err)
	}
	defer rows.Close()
	var retrievedCourses []courses.Course

	for rows.Next() {
		var course courses.Course
		err := rows.Scan(&course.Uuid, &course.Created, &course.Name, &course.Rating, &course.Descripton, &course.DurationMillis)
		if err != nil {
			fmt.Printf("Error parsing course row %v\n", err)
		}
		retrievedCourses = append(retrievedCourses, course)
	}
	return retrievedCourses

}

func (repo *PostgresCoursesRepository) CreateCourse(course courses.Course) {
	insert := `INSERT INTO courses(uuid, created, name, rating, description, duration_millis) 
			   VALUES (gen_random_uuid(), now(), $1, $2, $3, $4 );`
	_, err := repo.dbPool.Exec(context.Background(), insert, course.Name, course.Rating, course.Descripton, course.DurationMillis)
	if err != nil {
		fmt.Printf("Error inserting course into db: %v\n", err)
	}
}

The last thing we need to do in terms of Go code is to hook this up in our main package. This will be in our main.go file in the root of our module as follows:

import (
	"log"
	"net/http"
	"swapknowhow/courses/api"
	"swapknowhow/courses/internal/courses/db/postgres"
)

func main() {
	coursesApi := api.Api{CoursesRepo: postgres.NewPostgresCoursesRepository()}
	http.HandleFunc("/courses", coursesApi.Courses)
	log.Println("starting courses service on port 8082")
	err := http.ListenAndServe(":8082", nil)
	if err != nil {
		log.Fatal("could not start courses service")
	}
}

Running Postgres in Kubernetes

So, with our Go code in place, we need to turn our attention to our infrastructure. How do we spin up our postgres db locally. We’ll can initially do this by deploying to our local Kubernetes cluster directly with the kubectl command line interface. Later, we will do this via helm instead. Then we can take it further and package up our courses-service code into an image that can also be deployed to our Kubernetes cluster with helm – Scaffold will help us out here!

Lets create a postgres Pod spec and deploy it to our local Kubernetes cluster running through Rancher Desktop. When Rancher Desktop is up and running, it automatically configures the kubectl command line client to connect to the Kubernetes cluster that Rancher Desktop is running on its lightweight VM. We can confirm this by running the following command in our terminal.

kubectl config current-context

This should print

rancher-desktop

In a previous blog post, I described the main types of manifests that can be given to a Kubernetes cluster via the Kubernetes API in order to give it the specifications of what we want it to deploy. In order to run postgres in Kubernetes, we need to create a Kubernetes manifest containing a Pod specification and also, if we want data to persist across pod restarts, a PersistentVolume and PersistentVolumeClaim specification. This is can all be specified in one file. We’ll call it “postgres-pod.yaml” and it is in the directory structure shown below:

The Pod specification includes the name of our pod, the postgres image to use and postgres environment variables among other specificaitons. It also contains a lifecycle hook with a shell command that will wait until postgres is up before executing a sql command to create our courses table:

      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c", "apt update && apt install -y netcat && while ! nc -z localhost 5432; do sleep 1; done && psql -U postgres -d coursesdb -c 'CREATE TABLE IF NOT EXISTS courses(uuid uuid, created timestamp, name varchar, rating int, description varchar, duration_millis int);'"]

The full specification along with the perisitent volume spec is shown below:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: coursesdb
  name: coursesdb
spec:
  containers:
    - args:
        - postgres
      env:
        - name: POSTGRES_DB
          value: coursesdb
        - name: PGDATA
          value: coursesdb
        - name: POSTGRES_PASSWORD
          value: password
      image: docker.io/library/postgres:latest
      name: coursesdb
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c", "apt update && apt install -y netcat && while ! nc -z localhost 5432; do sleep 1; done && psql -U postgres -d coursesdb -c 'CREATE TABLE IF NOT EXISTS courses(uuid uuid, created timestamp, name varchar, rating int, description varchar, duration_millis int);'"]
      resources:
        limits:
          memory: "128Mi"
          cpu: "500m"
      ports:
        - containerPort: 5432
          hostPort: 5432
      volumeMounts:
        - mountPath: /var/lib/postgresql/data
          name: coursesdb-pv-claim
  volumes:
    - name: coursesdb-pv-claim
      persistentVolumeClaim:
        claimName: postgres-pv-claim
---
kind: PersistentVolume
apiVersion: v1
metadata:
  name: postgres-pv-volume
  labels:
    type: local
    app: coursesdb
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  hostPath:
    path: "/mnt/data"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: postgres-pv-claim
  labels:
    app: postgres
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

Okay, so with all that in place, now we can send it to the Kubernetes API of our local Rancher Desktop cluster via the kubectl command line tool by running the below in our terminal from the root of our module.

kubectl apply -f build/kubernetes/postgres-pod.yaml

Executing the following should show that the pod started up okay.

kubectl get events

This should output something like this (it won’t be exactly the same):

LAST SEEN   TYPE     REASON      OBJECT          MESSAGE
42s         Normal   Scheduled   pod/coursesdb   Successfully assigned default/coursesdb to lima-rancher-desktop
42s         Normal   Pulling     pod/coursesdb   Pulling image "docker.io/library/postgres:latest"
39s         Normal   Pulled      pod/coursesdb   Successfully pulled image "docker.io/library/postgres:latest" in 2.605928168s
39s         Normal   Created     pod/coursesdb   Created container coursesdb
39s         Normal   Started     pod/coursesdb   Started container coursesdb

With our postgres pod up and running in our local Kubernetes cluster, we need to be able to connect to it. We can do this by setting up port-forwarding to the pod by running the following in our terminal:

kubectl port-forward coursesdb 5432:5432

With our postgres db running, we can start up our microservice by running this from the root of our module:

go run .

Now we can use something like Postman to take our API for a spin

Packaging our system as a Helm release

Helm is a package manager for Kubernetes. It allows us to template out the Kubernetes specifications that need to be sent to our Kubernetes cluster in order to bring our desired system to life. A helm chart is a declarative way for defining how we want to template and package the system that we are deploying to Kubernetes. We can then create instances of this chart by providing values for helm to inject into the chart templates. Helm calls an instance a “release”. A release is an instance of our helm-packaged system running in a Kubernetes cluster.

We can create a skeleton for our helm chart in our build directory by running the following in that directory:

helm create courses

After running this, we can see that helm has created the following files and directories:

The template directory contains templated Kubernetes manifests. The values.yaml contains default values to be injected into these templates before they are applied to Kubernetes when a helm release is being created. The Chart.yaml contains a description of our helm chart. Also .helmignore is where we can specify files and directories to be ignored by helm.

So lets start with just converting our postgres pod and persistent volume into helm templates.

Inside the templates directory we can remove everything. Then we can create a templated specification for our postgres coursesdb by creating a file called (name is arbitrary) postgres-pod.yaml

The contents of this will be pretty similar to the postgres-pod.yaml we had before except certain parts will be templated so that we can get helm to inject values. The contents is as follows:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: {{.Release.Name}}-coursesdb
  name: {{.Release.Name}}-coursesdb
spec:
  containers:
    - args:
        - postgres
      env:
        - name: POSTGRES_DB
          value: {{.Release.Name}}-coursesdb
        - name: PGDATA
          value: {{.Release.Name}}-coursesdb
        - name: POSTGRES_PASSWORD
          value: {{.Values.coursesdb.password}}
      image: {{.Values.coursesdb.image}}
      name: {{.Release.Name}}-coursesdb
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c", "apt update && apt install -y netcat && while ! nc -z localhost 5432; do sleep 1; done && psql -U postgres -d {{.Release.Name}}-coursesdb -c 'CREATE TABLE IF NOT EXISTS courses(uuid uuid, created timestamp, name varchar, rating int, description varchar, duration_millis int);'"]
      resources:
        limits:
          memory: "128Mi"
          cpu: "500m"
      ports:
        - containerPort: 5432
          hostPort: 5432
      volumeMounts:
        - mountPath: /var/lib/postgresql/data
          name: {{.Release.Name}}-coursesdb-pv-claim
  volumes:
    - name: {{.Release.Name}}-coursesdb-pv-claim
      persistentVolumeClaim:
        claimName: {{.Release.Name}}-postgres-pv-claim

Helm uses Go templates. Parameters to be injected are referenced inside double curlies

{{}}

When helm creates a release, it binds values to these parameters by referencing fields and functions specified on objects. There is a built in object called Release. This has a field called Name. To reference this Name field, we use {{.Release.Name}}. This is used in quite a few places in the template so that things like the db name will be prefixed with the name of the helm release. When we instruct helm to create a release, we pass the release name as a command line argument. Helm then assigns this to the Release.Name field which is why it is available when helm is rendering our templates. In the above template, we have also templated out other parts by referencing fields on a Values object. This values object is manifested from the values.yaml file that I mentioned earlier. This has default values but they can also be overwritten in different ways – one of which is by passing the path to a values file as a command line argument when creating a helm release. For example, I have the POSTGRES_PASSWORD templated as {{.Values.coursesdb.password}}. This value is specified in the values.yaml as:

coursesdb:
  password: password

Really a password such as this should be coming from a secret management system but that is beyond the scope of this blog post.

We can template out our persistent volume in a file in templates called (arbitrary name) postgres-persistent-volume.yaml. Its contents are as follows:

kind: PersistentVolume
apiVersion: v1
metadata:
  name: {{.Release.Name}}-postgres-pv-volume
  labels:
    type: local
    app: {{.Release.Name}}-coursesdb
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  hostPath:
    path: "/mnt/data"

Also we need to template out our persistent volume claim. This can be done in a file (again, arbitrary name) called, say, postgres-persistent-volume-claim.yaml

Its contents are as follows:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: {{.Release.Name}}-postgres-pv-claim
  labels:
    app: postgres
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

We just need to then make sure we have our values added to the default values that helm created when it created the values.yaml. So we need to add the following to that file:

coursesdb:
  password: password
  image: docker.io/library/postgres:latest

We can create a release of this helm chart called “local” by running the following in the build/courses directory.

helm install local . 

To see that this has worked, after a few minutes, when we run

kubectl get pods

local-coursesdb   1/1     Running   0          23s

We need to port-forward to our postgres pod as before:

kubectl port-forward local-coursesdb 5432:5432

We also need one minor change to tell Go code to connect to a db now called local-coursesdb. This is in postgres_repository.go

func NewPostgresCoursesRepository() *PostgresCoursesRepository {
	connection := "user=postgres password=password host=localhost port=5432 dbname=local-coursesdb"
...
...

With this in place we can run our courses-service from the root directory of our module

go run .

Making our courses-service configurable

Lets get rid of the hard-coding of the postgres connection string in postgres_repository.go. We can make this configurable by creating a struct to carry the config and making a small change to our NewPostgresCoursesRepository function. Changes to postgres_repository.go are shown below:

type PostgresConfig struct {
	User         string
	Password     string
	Host         string
	Port         int
	DatabaseName string
}

func NewPostgresCoursesRepository(config PostgresConfig) *PostgresCoursesRepository {
	connection := fmt.Sprintf("user=%s password=%s host=%s port=%d dbname=%s",
		config.User, config.Password, config.Host, config.Port, config.DatabaseName)
...
...

Now we can pass in config from main.go as follows:

func main() {
	coursesApi := api.Api{CoursesRepo: postgres.NewPostgresCoursesRepository(
		postgres.PostgresConfig{
			User:         "postgres",
			Password:     "password",
			Host:         "localhost",
			Port:         5432,
			DatabaseName: "local-coursesdb"})}
...
...

Let’s go a step further and make our service configurable via environment variables. Luckily the Go standard library makes consuming environment variables very straight forward.

With the below change in main.go, we can consume from environment variables instead

func main() {
	pgUser, exists := os.LookupEnv("POSTGRES_USER")
	if !exists {
		log.Fatal("No POSTGRES_USER env variable")
	}
	pgPasswrod, exists := os.LookupEnv("POSTGRES_PASSWORD")
	if !exists {
		log.Fatal("No POSTGRES_PASSWORD env variable")
	}
	pgHost, exists := os.LookupEnv("POSTGRES_HOST")
	if !exists {
		log.Fatal("No POSTGRES_HOST env variable")
	}
	pgPort, exists := os.LookupEnv("POSTGRES_PORT")
	if !exists {
		log.Fatal("No POSTGRES_PORT env variable")
	}
	pgPortInt, err := strconv.Atoi(pgPort)
	if err != nil {
		log.Fatal("No POSTGRES_PORT env variable must be a number")
	}
	pgDbName, exists := os.LookupEnv("POSTGRES_DB_NAME")
	if !exists {
		log.Fatal("No POSTGRES_DB_NAME env variable")
	}

	coursesApi := api.Api{CoursesRepo: postgres.NewPostgresCoursesRepository(
		postgres.PostgresConfig{
			User:         pgUser,
			Password:     pgPasswrod,
			Host:         pgHost,
			Port:         pgPortInt,
			DatabaseName: pgDbName})}
...
...

To run this we’ll need to set the required environment variable first on the command line

export POSTGRES_USER=postgres     
export POSTGRES_PASSWORD=password
export POSTGRES_HOST=localhost
export POSTGRES_PORT=5432
export POSTGRES_DB_NAME=local-coursesdb
go run .

Creating an image of the courses-service

In order to run our courses-service in Kubernetes, we will need to create an image. This can be done via Docker – the Docker command line interface also comes with Rancher Desktop. We just need a Dockerfile in the root of our module containing the following:

FROM golang:1.18-alpine

WORKDIR /swapknowhow/courses

COPY go.mod ./

RUN go mod tidy

EXPOSE 8084

COPY . .
COPY /api ./api
COPY /internal ./internal
COPY *.go ./

RUN go build

CMD ["./courses"]

Now we can build a local image by running the following in the root of our module:

docker build --tag swapknowhow/courses-service .

Running it all on Kubernetes via Helm

Now that we made our courses-service configurable, it will be a lot easier to deploy it on Kubernetes and pass in the environment variables it needs via a Kubernetes ConfigMap. Let’s deploy it as a Kubernetes Deployment behind a Kubernetes Service. I gave an overview of Kubernetes Pods, Deployments and Services in a previous post. We can put all of these specifications into one file as a helm template. Let’s call this courses-service.yaml and it goes in the same directory as our other helm templates.

The contents of this file contains the template for the Deployment, ConfigMap and Service and is as follows:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{.Release.Name}}-courses-service-deployment
spec:
  selector:
    matchLabels:
      app: {{.Release.Name}}-courses-service
  replicas: 1
  template:
    metadata:
      labels:
        app: {{.Release.Name}}-courses-service
    spec:
      containers:
        - name: {{.Release.Name}}-courses-service
          image: {{ .Values.courses.image }}
          imagePullPolicy: IfNotPresent
          ports:
              - containerPort: 8084
          envFrom:
            - configMapRef:
                name: courses-config
---
apiVersion: v1
kind: ConfigMap
metadata: 
  name: courses-config
data: 
  POSTGRES_USER: {{ .Values.courses.postgres.user | quote}}
  POSTGRES_PASSWORD: {{ .Values.courses.postgres.password | quote }}
  POSTGRES_HOST: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
  POSTGRES_PORT: {{ .Values.courses.postgres.port | quote }}
  POSTGRES_DB_NAME: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}

---
apiVersion: v1
kind: Service
metadata:
  name: {{.Release.Name}}-courses-service
  labels:
    app: {{.Release.Name}}-courses-service
spec:
  type: NodePort
  ports:
    - port: 8082
  selector:
    app: {{.Release.Name}}-courses-service

One thing to note here is that the variables on the config map have their values coming from our values.yaml file. e.g. for postgres user

{{ .Values.courses.postgres.user | quote}}

The value is piped through a template function called quote to surround it in quotes. Another part to note here is:

image: {{ .Values.courses.image }}
imagePullPolicy: IfNotPresent

The name of the image corresponds the the docker image we built earlier. It will be injected into the template from values.yaml. Also, note that we specify IfNotPresent for the image pull policy. This means that the local image we built earlier will be used.

We need our values.yaml to match up and the values that our template expects.

We can add the following to values.yaml.

courses:
    image: swapknowhow/courses-service:latest
    postgres:
      user: postgres
      password: password
      port: 5432
      dbName: coursesdb

In order for our courses-service Kubernetes Service to talk to our coursesdb, we need to extend the postgres-pod.yaml template we created earlier. Lets rename this file postgres-service.yaml. In this file, instead of defining the postgres pod directly, we will define a Kubernetes Deployment and a Kubernetes Service. The contents of this file is as follows:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}-deployment
spec:
  selector:
    matchLabels:
      run: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
      app: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
  replicas: 1
  template:
    metadata:
      labels:
        run: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
        app: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
    spec:
      containers:
      - args:
          - postgres
        env:
          - name: POSTGRES_DB
            value: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
          - name: PGDATA
            value: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
          - name: POSTGRES_PASSWORD
            value: {{.Values.coursesdb.password}}
        image: {{.Values.coursesdb.image}}
        name: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
        lifecycle:
          postStart:
            exec:
              command: ["/bin/sh", "-c", "apt update && apt install -y netcat && while ! nc -z localhost 5432; do sleep 1; done && psql -U postgres -d {{.Release.Name}}-{{ .Values.courses.postgres.dbName }} -c 'CREATE TABLE IF NOT EXISTS courses(uuid uuid, created timestamp, name varchar, rating int, description varchar, duration_millis int);'"]
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
          - containerPort: 5432
            hostPort: 5432
        volumeMounts:
          - mountPath: /var/lib/postgresql/data
            name: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}-pv-claim
      volumes:
        - name: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}-pv-claim
          persistentVolumeClaim:
            claimName: {{.Release.Name}}-postgres-pv-claim
---
apiVersion: v1
kind: Service
metadata:
  name: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
  labels:
    app: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}
spec:
  ports:
    - port: 5432
      targetPort: 5432 
  selector:
    app: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}

One thing to note here is that we have named the service with templated values

name: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}

This is important as it will be the postgres hostname our courses-service microservice will be injected with in order for it to connect to the this postgres service. This can be seen if we look back at courses-service.yaml from earlier at this line:

POSTGRES_HOST: {{.Release.Name}}-{{ .Values.courses.postgres.dbName }}

The Kubernetes cluster that Rancher Desktop creates includes CoreDNS which allows service discovery by way of the namespace and name of the service. We also need a couple more values in our values.yaml to be consumed by this template:

coursesdb:
  password: password
  image: docker.io/library/postgres:latest

Again, the full code listing is available on my Github. With all of this in place, lets deploy our system to our local Kubernetes cluster using helm! Lets delete our previous helm release as follows:

helm delete local

Now to deploy our new release, we can run the below in the root of our module.

helm install local build/courses

We should see something similar to the following:

NAME: local
LAST DEPLOYED: Fri Apr 15 19:23:15 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1

We can see our services on Kubernetes by running the following:

kubectl get svc

This will show something similar to the following:

kubernetes              ClusterIP   10.43.0.1       <none>        443/TCP          9d
local-coursesdb         ClusterIP   10.43.52.88     <none>        5432/TCP         2m17s
local-courses-service   NodePort    10.43.127.123   <none>        8082:32203/TCP   2m17s

So that we can make requests to our courses-service and also connect to the db to test things out, we can set up port forwarding as follows:

kubectl port-forward services/local-courses-service 8082:8082

and in a separate terminal tab

kubectl port-forward services/local-coursesdb 5432:5432

Now, again, we can make a request in Postman to create a course:

I use Datagrip to connect to databases. After creating a course, and thanks to the port forwarding we set up, I can run a query for it on the postgres db (running inside our Kubernetes).

select * from courses;

with the result shown below:

I can retrieve the created course by calling our courses-service through Postman.

Simplifying our workflow with Skaffold

Skaffold allows us to make code changes and have them automatically deployed to our Kubernetes cluster. It also sets up automatic port forwarding to our Kubernetes services so that we don’t have to do that manually like we did earlier. Instructions to install Skaffold are here

In MacOS it can be installed as follows:

brew install skaffold

After skaffold is installed we can add a skaffold.yaml file at the root of our module containing the following:

build:
  local:
    push: false
  artifacts:
    - image: swapknowhow/courses-service
kind: Config
deploy:
  helm:
    releases:
      - name: local
        chartPath: ./build/courses
        artifactOverrides:
          imageKey: swapknowhow/courses-service
          image: swapknowhow/courses-service
        imageStrategy:
          fqn: {}

Skaffold tracks an image being built by helm so that it can substitute it. In this way, Skaffold, in dev mode, can monitor changes to our source code, and rebuild the image and redeploy the necessary Pod in our Kubernetes cluster. It has a number of strategies for doing this. One is called fqn and is the default so, technically, it could have been left out of the config above. It references an image in helm through an image key which we specified in the above configuration as:

imageKey: swapknowhow/courses-service 

This should not include the tag. We need a small change to our helm template, courses-service.yaml. The image for the container needs to be set as follows:

image: {{ .Values.imageKey }}

We also need to add the following to our values.yaml:

imageKey: swapknowhow/courses-service:latest

Skaffold will actually override the above value.

So, now we can get our helm release up and running through Skaffold. We will do this in dev mode so that Skaffold will watch for code changes and re-build the image and re-depoly for us. Skaffold takes care of port-forwarding for us automatically if we also include a parameter in our command below:

skaffold dev --port-forward

Running this command will output something like the following:

Listing files to watch...
 - swapknowhow/courses-service
Generating tags...
 - swapknowhow/courses-service -> swapknowhow/courses-service:84d44f8-dirty
Checking cache...
 - swapknowhow/courses-service: Found Locally
Tags used in deployment:
 - swapknowhow/courses-service -> swapknowhow/courses-service:2c6ce8400bcc0d8b59095569248e00b1d81184ee4c3053d1d388822aaf80506d
Starting deploy...
Helm release local not installed. Installing...
NAME: local
LAST DEPLOYED: Fri Apr 15 21:01:22 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
Waiting for deployments to stabilize...
 - deployment/local-courses-service-deployment is ready. [1/2 deployment(s) still pending]
 - deployment/local-coursesdb-deployment: waiting for rollout to finish: 0 of 1 updated replicas are available...
 - deployment/local-coursesdb-deployment is ready.
Deployments stabilized in 24.184 seconds
Port forwarding service/local-coursesdb in namespace default, remote port 5432 -> http://127.0.0.1:5432
Port forwarding service/local-courses-service in namespace default, remote port 8082 -> http://127.0.0.1:8082
...
...
[local-courses-service] 2022/04/15 20:03:34 starting courses service on port 8082

Now let’s make a code change! In courses_api.go, let’s add a print statement when handling a POST request to create a course.

	case "POST":
		fmt.Println("Creating Course")
		api.createCourse(writer, req)

On save, we can see Skaffold re-build the swapknowhow/courses-service image with similar output as before.

Now if we hit the courses-service endpoint to create a course from Postman, we see the following in the logs emitted by Skaffold.

Watching for changes...
[local-courses-service] Creating Course

Isn’t that amazing! We can make live code changes and have them deployed to our local Kubernetes cluster on the spot!

Goland Cloud Code plugin

This is made even simpler with the Cloud Code plugin for Goland.

With this installed, it detects our Skaffold configuration automatically.

We can simply click to create a cloud code Kubernetes run configuration. The plugin will also install a managed Google Cloud SDK

When that is all complete, we will see our run configuration.

We can kill our currently running skaffold dev with ctrl-c in the terminal. Now we can click play to run skaffold through Goland. It might give this error:

parsing skaffold config: error parsing skaffold configuration file: unknown skaffold config API version "skaffold/v2beta28". Set the config 'apiVersion' to a known value. Check https://skaffold.dev/docs/references/yaml/ for the list of valid API versions. 

If so, we can just use an earlier version of the Skaffold api, e.g. skaffold/v2beta24

So, we just need to change the top line of our skaffold.yaml to:

apiVersion: skaffold/v2beta24

Also, I found sometimes, running skaffold either on the command line or through the Cloud Code plugin after it has been run successfully before can output something like:

Error: UPGRADE FAILED: "local" has no deployed releases
deploying "local": install: exit status 1
Cleaning up...
release "local" uninstalled

If this happens, Skaffold will have cleaned up itself so simply run it again through the Cloud Code plugin or from the command line.

So, we can go ahead and click that play button in Goland and this time we will see output like we did when Skaffold ran successfully from the command line, only this time, the output will be in Goland and will eventually emit the log from our courses-service:

[local-courses-service] 2022/04/15 20:39:04 starting courses service on port 8082

Now we can create and retrieve courses as we did before by hitting our courses-service endpoints from Postman!

Conclusion

It can take a little bit to get everything set up. However, when our Helm chart and Skaffold config is set up, we get a wonderful dev experience right on top of a local Kubernetes cluster. Also, once it is all set up like this, it makes it seamless for other engineers on your team to spin up the exact same dev environment on their machines. The amazing thing is, it doesn’t just have to be on our local cluster. We could easily point kubectl to a Kubernetes cluster running in the cloud e.g. Google Kubernetes Engine (GKE) and get the same ephemeral environment while writing our code locally! Thanks for reading through to the end of this post. I had great fun writing it and building out the courses-service microservice in Go right on top of a Kubernetes cluster running locally. The full code is on my Github.

For updates on new posts, you can follow me on twitter or linkedin.

Keep Jazz Bands Valid with Java 17

In a previous blog post I talked about modelling business rules with types. I introduced a concept called Algebraic Data Types (ADTs). This is a concept I used a lot in 2017/2018 when I was programming in F# a lot – for my day job and side-projects. In F#, ADTs are provided by what are called Discriminated Unions. I haven’t really used F# at all since late 2018. These days I’m mostly using Java, Go, JS and Typescript between work and play. A lot of my day to day work involves microservice development. I find I don’t have too much use for ADTs in this context. Microservices are, by their nature, very thin – usually taking data from somewhere; doing some transformations on it; and sending it on its merry way. Nevertheless, ADTs are still a very useful concept when it comes to domain modelling.

With Java 17 being recently released, I was keen to try out its new features to see if they would give me enough to model data and business rules with ADTs. To give this a go, let’s take what I implemented in that previous post. Let’s see if we can keep our Jazz Bands valid. This time, we’ll use Java 17 instead of Scala!

Okay, so ADTs sound pretty out there! They are actually pretty practical though when it comes to modelling a domain and using the compiler to make illegal states unrepresentable. It’s all about the word “AND” and the word “OR”. A data type can be a “product” type meaning its made of “one thing” AND “another thing” AND “another thing”…..etc. It can also be a “sum” type meaning it’s made up of “one thing” OR “another thing” OR “another thing” …. etc.

Okay, let’s put this into practical terms. Let’s say we have some sort of music application that processes data related to jazz bands. This application is only interested in three different types of jazz band. It wants a jazz band to only ever be either a Piano Trio OR a Quartet OR a Quintet. Also, when we say something is a Piano Trio, we want to be damn sure that it is the type of Piano Trio that the application expects. It wants a Piano Trio to be only made up of a Piano Player AND a Bass Player AND a Drummer. Furthermore, it wants a Quartet to only be made up of a Trumpet Player AND a Piano Player AND a Bass Player AND a Drummer. Finally, it wants a Quintet to be made up of only a Sax Player AND a Trumpet Player AND a Piano Player AND a Bass Player AND a Drummer.

We can model this with some pseudo-code to make the domain a bit clearer:

type PianoTrio = PianoPlayer AND BassPlayer AND Drummer
type Quartet = TrumpetPlayer AND PianoPlayer AND BassPlayer AND Drummer
type Quintet = SaxPlayer AND TrumpetPlayer AND PianoPlayer AND BassPlayer AND Drummer

type JazzBand = PianoTrio OR Quartet OR Quintet

So we can see from the above that each of our JazzBands is a “product” (“AND”) type. Let’s put each one of these together with Java records. For now, we will model each musician in the bands with just a string for their name. We will improve on this later.

public class DomainV1 {

  public static record PianoTrio(
      String pianoPlayer,
      String bassPlayer,
      String drummer){}

  public static record Quartet(
      String trumpetPlayer,
      String pianoPlayer,
      String bassPlayer,
      String drummer){}

  public static record Quintet(
      String saxPlayer,
      String trumpetPlayer,
      String pianoPlayer,
      String bassPlayer,
      String drummer){}
}

Now, let’s say we have a function in our application logic that decides which musician in each type of jazz band is the leader of the band. This function wants to be given a jazz band but it wants to be completely safe in the knowledge that it will only ever be given a properly constructed PianoTrio OR a Quartet OR a Quintet. This sounds like a “sum” (“OR”) type to me. In Java 17, this can be done using a sealed interface. A sealed interface means that the interface can only be implemented by a specified discrete set of types. In our case, we need a jazz band to be modelled using a sealed interface and we want to permit this to only ever be implemented by either a PianoTrio, a Quartet, or a Quintet. This is handled in the following code:

public class DomainV1 {

  public sealed interface JazzBand permits PianoTrio, Quartet, Quintet {}

  public static record PianoTrio(
      String pianoPlayer,
      String bassPlayer,
      String drummer) implements JazzBand {}

  public static record Quartet(
      String trumpetPlayer,
      String pianoPlayer,
      String bassPlayer,
      String drummer) implements JazzBand {}

  public static record Quintet(
      String saxPlayer,
      String trumpetPlayer,
      String pianoPlayer,
      String bassPlayer,
      String drummer) implements JazzBand {}
}

We can see that the Java sealed interface, JazzBand, specifies exactly what types it permits to implement it. Now our function to get the leader of a band can accept the JazzBand sealed interface as a parameter and it is completely safe in the knowledge that it will only ever be given a PianoTrio or a Quartet or a Quintet. To implement this function we can take advantage of pattern matching in Java 17. Pattern matching allows us to take a sealed interface type and match on its concrete type. As it is a sealed interface type, the compiler knows which concrete types are permitted. This is available in Java as instanceof. However it doesn’t allow for exhaustive pattern matching (where the compiler tells us if we’ve missed a possible concrete type). So, in the function below, we still need to return something as a default – the empty string in this case.

public class AppV1 {

  public static String getBandLeader(DomainV1.JazzBand jazzBand){
    if (jazzBand instanceof DomainV1.PianoTrio pianoTrio) {
      return pianoTrio.pianoPlayer();
    }
    if (jazzBand instanceof DomainV1.Quartet quartet) {
      return quartet.trumpetPlayer();
    }
    if (jazzBand instanceof DomainV1.Quintet quintet) {
      return quintet.saxPlayer();
    }
    return "";
  }

  public static void main(String[] args) {
    final var pianoTrio = new DomainV1.PianoTrio(
        "Bill Evans",
        "Scott LaFaro",
        "Paul Motian");
    final var quartet = new DomainV1.Quartet(
        "Miles Davis",
        "Horace Silver",
        "Percy Heath",
        "Max Roach");
    final var quintet = new DomainV1.Quintet(
        "Sonny Rollins",
        "Kenny Dorham",
        "Thelonious Monk",
        "Percy Heath",
        "Art Blakey");
    System.out.println(getBandLeader(pianoTrio));
    System.out.println(getBandLeader(quartet));
    System.out.println(getBandLeader(quintet));
  }
}

instanceof does allow us to bind to a new variable which is automatically casted to the concrete type we are checking on though. For example, when the check for instanceof on PianoTrio is true, we can bind the jazzBand variable to a variable of type PianoTrio, called pianoTrio in the above example. Then we can access fields on the PianoTrio record: pianoTrio.pianoPlayer()

So instanceof with the smart casting that Java provides is nice but it doesn’t really give use full safety. If a new type of JazzBand is created and we don’t handle it, the compiler won’t let us know.

As a preview feature in Java 17, pattern matching on switch has been added. This allows us to do exhaustive checks on the type of our JazzBand. Now we can implement the getBandLeader function as:

public class AppV1 {

  public static String getBandLeader(DomainV1.JazzBand jazzBand){
    return switch (jazzBand) {
      case DomainV1.PianoTrio pianoTrio -> pianoTrio.pianoPlayer();
      case DomainV1.Quartet quartet -> quartet.trumpetPlayer();
      case DomainV1.Quintet quintet -> quintet.saxPlayer();
    };
  }

  public static void main(String[] args) {

    final var pianoTrio = new DomainV1.PianoTrio(
        "Bill Evans",
        "Scott LaFaro",
        "Paul Motian");
    final var quartet = new DomainV1.Quartet(
        "Miles Davis",
        "Horace Silver",
        "Percy Heath",
        "Max Roach");
    final var quintet = new DomainV1.Quintet(
        "Sonny Rollins",
        "Kenny Dorham",
        "Thelonious Monk",
        "Percy Heath",
        "Art Blakey");
    System.out.println(getBandLeader(pianoTrio));
    System.out.println(getBandLeader(quartet));
    System.out.println(getBandLeader(quintet));
  }
}

One thing to notice is that the switch expression is an expression. This means that it evaluates to something. In the above code, it evaluates to a String that we can just return from the function. Pattern matching on switch is similar to pattern matching on instanceof with smart casting. However, it also makes us handle all of the cases. If we leave out a case for one of our concrete JazzBand types, we get a compilation error. So now we can be safe in the knowledge that our getBandLeader function has handled all of the types of JazzBand that exist in our application. The compiler has our backs!

Okay so this is great, we have encoded the constraint that our JazzBands should only ever be a PianoTrio, a Quartet or a Quintet. However, we’re still not fully protected. There are still illegal states that should not happen but that can still happen in our code. Each of our JazzBand concrete types is made up of Strings for each musician. So, there is nothing in the code to stop someone creating a PianoTrio with three drummers!

It’s time for some more types! Instead of passing around a String for a piano player, let’s create a type. We could do this by, again, using a Java record

record PianoPlayer(String name){}

However, this still doesn’t really solve the problem. There is nothing to stop someone creating a PianoPlayer by passing in the name of a bass player! To make sure that we can only create a valid PianoPlayer, we can use a regular Java class, keep the constructor private and provide a static method to return an Optional<PianoPlayer>. If the String that is passed to this method is null or contains the name of someone who is not a piano player, the method will return an empty Optional. Otherwise it will return a PianoPlayer wrapped up in an Optional. The same pattern can be used for the other types of musicians, BassPlayer, TrumpetPlayer, SaxPlayer and Drummer. We will keep things simple and just hard-code the names that are valid for each type of musician. This is all shown below:

public class DomainV2 {

  public static class PianoPlayer {
    private final String name;

    private PianoPlayer(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public static Optional<PianoPlayer> validPianoPlayer(final String name) {
      final var validPianoPlayers = Set.of("Bill Evans", "Horace Silver", "Thelonious Monk");
      if (name != null && validPianoPlayers.contains(name)) {
        return Optional.of(new PianoPlayer(name));
      }
      return Optional.empty();
    }
  }

  public static class TrumpetPlayer{
    private final String name;

    private TrumpetPlayer(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public static Optional<TrumpetPlayer> validTrumpetPlayer(final String name) {
      final var validTrumpetPlayers = Set.of("Miles Davis", "Kenny Dorham");
      if (name != null && validTrumpetPlayers.contains(name)) {
        return Optional.of(new TrumpetPlayer(name));
      }
      return Optional.empty();
    }
  }
  public static class SaxPlayer{
    private final String name;

    private SaxPlayer(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public static Optional<SaxPlayer> validSaxPlayer(final String name) {
      final var validSaxPlayers = Set.of("John Coltrane", "Sonny Rollins");
      if (name != null && validSaxPlayers.contains(name)) {
        return Optional.of(new SaxPlayer(name));
      }
      return Optional.empty();
    }
  }

  public static class BassPlayer{
    private final String name;

    private BassPlayer(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public static Optional<BassPlayer> validBassPlayer(final String name) {
      final var validBassPlayers = Set.of("Paul Chambers", "Scott LaFaro", "Percy Heath");
      if (name != null && validBassPlayers.contains(name)) {
        return Optional.of(new BassPlayer(name));
      }
      return Optional.empty();
    }
  }

  public static class Drummer {
    private final String name;

    private Drummer(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public static Optional<Drummer> validDrummer(final String name) {
      final var validDrummers =
          Set.of("Philly Joe Jones", "Art Blakey", "Paul Motian", "Max Roach");
      if (name != null && validDrummers.contains(name)) {
        return Optional.of(new Drummer(name));
      }
      return Optional.empty();
    }
  }
}

We can take this for a spin with a new version of our App class:

public class AppV2 {

    public static String getBandLeader(DomainV2.JazzBand jazzBand){
        return switch (jazzBand) {
            case DomainV2.Quartet quartet -> quartet.trumpetPlayer().getName();
            case DomainV2.PianoTrio pianoTrio -> pianoTrio.pianoPlayer().getName();
            case DomainV2.Quintet quintet -> quintet.saxPlayer().getName();
        };
    }

    public static void main(String[] args) {
        final var pianoPlayer =
            DomainV2.PianoPlayer.validPianoPlayer("Bill Evans")
                .orElseThrow(() -> new RuntimeException("invalid piano"));

        final var trumpetPlayer = DomainV2.TrumpetPlayer.validTrumpetPlayer("Miles Davis")
            .orElseThrow(() -> new RuntimeException("invalid trumpet"));
        final var saxPlayer = DomainV2.SaxPlayer.validSaxPlayer("John Coltrane")
            .orElseThrow(() -> new RuntimeException("invalid sax"));
        final var bassPlayer = DomainV2.BassPlayer.validBassPlayer("Paul Chambers")
            .orElseThrow(() -> new RuntimeException("invalid bass"));
        final var drummer = DomainV2.Drummer.validDrummer("Philly Joe Jones")
            .orElseThrow(() -> new RuntimeException("invalid drummer"));

        final var pianoTrio = new DomainV2.PianoTrio(pianoPlayer, bassPlayer, drummer);
        final var quartet = new DomainV2.Quartet(trumpetPlayer, pianoPlayer, bassPlayer, drummer);
        final var quintet = new DomainV2.Quintet(saxPlayer, trumpetPlayer, pianoPlayer, bassPlayer, drummer);

        System.out.println(getBandLeader(pianoTrio));
        System.out.println(getBandLeader(quartet));
        System.out.println(getBandLeader(quintet));
    }
}

Now having to create each valid musician before we can then combine them into a valid band is a little clunky. Using flatMap and map on Optional, we can chain the creation of each musician together and combine them into whatever JazzBand concrete type we are instantiating. For example, to create a valid PianoTrio, we can add a static method onto the PianoTrio type as follows:

  public static record PianoTrio(PianoPlayer pianoPlayer, 
                                 BassPlayer bassPlayer, 
                                 Drummer drummer) implements JazzBand {

    public static Optional<PianoTrio> validPianoTrio(String pianoPlayer, String bassPlayer, String drummer) {
      return
          PianoPlayer.validPianoPlayer(pianoPlayer)
            .flatMap(pPlayer -> BassPlayer.validBassPlayer(bassPlayer)
               .flatMap(bp -> Drummer.validDrummer(drummer)
                 .map(d -> new PianoTrio(pPlayer, bp, d))));
    }
  }

In the above validPianoTrio function, the first musician name that is null or not valid for the corresponding musician will cause an empty Optional to be returned from the whole chain. This in turn will cause an empty Optional to be returned from the function. This is handy because now we have a function that can take musician names as String parameters and it can do all of the validation for us. If the musician names make up a valid PianoTrio, we get back our PianoTrio wrapped up in an Optional. If any of the musicians are not valid, we get an empty Optional back instead.

The same pattern can be used for creating a valid Quartet and a valid Quintet. The only problem with this pattern is that, if we get back an empty Optional from the validPianoTrio function, we don’t know which musician name caused the problem.

We can get around this by introducing a Result type which can either be a Success or Error. This sounds like another sum (“OR”) type to me. Again, we can represent this as a sealed interface that permits 2 concrete types.

  public sealed interface Result<T> permits Success, Error {}
  public static record Success<T>(T value) implements Result<T> {}
  public static record Error<T>(List<String> errors) implements Result<T> {}

The Success case holds a value. The Error case holds a list of String errors. Now we can have a function to create a valid PianoTrio which processes the name Strings. If they all produce a Success for their corresponding musician, then we can create a PianoTrio and return it wrapped in a Success. If one or more of the String names produce an Error for their corresponding musician, then we can aggregate the error Strings and return them wrapped in one aggregated Error. The code for this is below. We take advantage of pattern matching on instanceof here with Java’s smart casting.

       public static Result<PianoTrio> validPianoTrioV2(String pianoPlayer,
                                                     String bassPlayer,
                                                     String drummer) {
      final Result<PianoPlayer> pp = PianoPlayer.validPianoPlayerResult(pianoPlayer);
      final Result<BassPlayer> bp = BassPlayer.validBassPlayerResult(bassPlayer);
      final Result<Drummer> dr = Drummer.validDrummerResult(drummer);

      if (pp instanceof Success<PianoPlayer> pPlayer &&
          bp instanceof Success<BassPlayer> bPlayer &&
          dr instanceof Success<Drummer> drr)  {
        return new Success<>(new PianoTrio(pPlayer.value, bPlayer.value, drr.value));
      }
      var errors = new ArrayList<String>();

      if (pp instanceof Error<PianoPlayer> ppErrors) {
        errors.addAll(ppErrors.errors);
      }

      if (bp instanceof Error<BassPlayer> bpErrors) {
        errors.addAll(bpErrors.errors);
      }

      if (dr instanceof Error<Drummer> drErrors) {
        errors.addAll(drErrors.errors);
      }
      return new Error<>(errors);
    }

The code above is a little long-winded but it does the job and and it is fairly clear what it is doing. This is probably the way I would write this function if I were to write it in practice. There is another way to do this though. It’s a little esoteric though and I probably wouldn’t use it in practice. However, it has been a while since I scratched the Functional Programming itch so lets just go through it for a bit of fun! It uses a concept called the applicative functor. It is a Functional Programming pattern for combining wrapper types such as the Result wrapper type that we have here. Using it, we can chain together Result objects that should hold valid musicians to make up a PianoTrio. If all the musicians in the chain are valid, we get a valid PianoTrio out of the chain. If one or more of the musicians in the chain are not valid, the errors get aggregated along the chain and we get an Error out of the chain with a list of the aggregated error Strings.

First of all, we need a function that takes the following parameters: a Result which wraps a Function which does some sort of transformation on a value; and a Result which wraps a value (in the Success case) which will be passed into the wrapped Function of our first parameter. This is shown below;

  public static <T, U> Result<U> resultFMap(Result<Function<T, U>> f, Result<T> v) {
    return switch (v) {
      case Success<T> s ->
          switch (f) {
            case Success<Function<T, U>> ss ->
                new Success<U>(ss.value().apply(s.value()));
            case Error<Function<T, U>> err -> new Error<U>(err.errors());
          };
      case Error<T> error ->
          switch (f) {
            case Success<Function<T, U>> ss -> new Error<>(error.errors);
            case Error<Function<T, U>> err -> {
              final var errors = new ArrayList<String>();
              errors.addAll(error.errors);
              errors.addAll(err.errors);
              yield new Error<U>(errors);
            }
          };
    };
  }

We take the v parameter. If it is a Success case, we check the parameter, f. If this is also a Success case, then we can apply the function that it contains to the value that v contains and wrap this up an a Success case. If f is an Error case, then we return a new Error case which wraps the same errors that f wraps.

If the v parameter is an Error case, we check the parameter, f. If f is also an Error case, then we combine the error Strings contained in v and the error Strings contained in f and return a new Error case with the combined error Strings. If f is a Success case, then we return a new Error case which wraps the error Strings that were wrapped by v.

With this resultFMap function in place, we can use it in a function to create a PianoTrio as follows:

    public static Result<PianoTrio> validPianoTrioV3(String pianoPlayer, 
                                                     String bassPlayer, 
                                                     String drummer) {

      final Function<PianoPlayer, Function<BassPlayer, Function<Drummer, PianoTrio>>> curried =
          (PianoPlayer pp) -> (BassPlayer bp) -> (Drummer d) -> new PianoTrio(pp, bp, d);

      return resultFMap(
          resultFMap(
            resultFMap(
              new Success<>(curried),
              PianoPlayer.validPianoPlayerResult(pianoPlayer)),
            BassPlayer.validBassPlayerResult(bassPlayer)),
          Drummer.validDrummerResult(drummer));
    }
  }

In this function, we create a local variable called curried. In Functional Programming a curried function is one where a function that takes multiple parameters is broken up into a series of outer to inner functions, each of which takes one parameter. So the curried variable here is equal to a curried version of the PianoTrio constructor.

We can then wrap the curried function in a Success case, new Success<>(curried). Because curried essentially takes 1 parameter and returns another function, we can apply it to a Result<PianoPlayer> type. If the Result<PianoPlayer> is a Success case, the PianoPlayer value it contains is passed in as an argument to curried by way of the resultFMap function we created earlier. If Result<PianoPlayer> is an Error case, then the resultFMap will add its errors Strings to an aggregated list of String errors and return these wrapped in a new Error case. This pattern continues along the chain for a Result<BassPlayer> and a Result<Drummer>. So, at the end of the chain, we return a result which is either a Success case containing a valid PianoTrio or an Error case containing an aggregated list of String errors.

We can take all of this for a spin below:

    public static void main(String[] args) {
        final var validPianoTrio =
            DomainV2.PianoTrio.validPianoTrioV3("Bill Evans", "Percy Heath", "Art Blakey");
        final var invalidPianoTrio1 =
            DomainV2.PianoTrio.validPianoTrioV3("Art Blakey", "Percy Heath", "Art Blakey");
        final var invalidPianoTrio2 =
            DomainV2.PianoTrio.validPianoTrioV3("Bill Evans", "Philly Joe Jones", "Art Blakey");
        final var invalidPianoTrio3 =
            DomainV2.PianoTrio.validPianoTrioV3("Bill Evans", "Percy Heath", "Bill Evans");
        final var invalidPianoTrio4 =
            DomainV2.PianoTrio.validPianoTrioV3("Bill Evans", "Bill Evans", "Miles Davis");
        System.out.println(validPianoTrio);
        System.out.println(invalidPianoTrio1);
        System.out.println(invalidPianoTrio2);
        System.out.println(invalidPianoTrio3);
        System.out.println(invalidPianoTrio4);
    }

We can also add a toString to each of the musician types e.g.

    public String toString() {
      return "PianoPlayer{" +
             "name='" + name + '\'' +
             '}';
    }

Now, when we execute the main function above, we get the below output:

Success[value=PianoTrio[pianoPlayer=PianoPlayer{name='Bill Evans'}, bassPlayer=BassPlayer{name='Percy Heath'}, drummer=Drummer{name='Art Blakey'}]]
Error[errors=[invalid piano player Art Blakey]]
Error[errors=[invalid bass player Philly Joe Jones]]
Error[errors=[invalid drummer Bill Evans]]
Error[errors=[invalid drummer Miles Davis, invalid bass player Bill Evans]]

The applicative functor pattern is a nice pattern. However, it is a bit esoteric so I wouldn’t really use it in day-to-day code that much.

Conclusion

This was a fun blog to write. Java 17 has really great features which provide the ability to model domains using ADTs. They also enable some of the more esoteric Functional Programming patterns usually found in languages like Haskell. I’m looking forward to when pattern matching on switch moves out of preview and becomes a main feature of the language. Thanks for reading!

Deliver rapidly with Kubernetes

There are many benefits to microservices. One of my favourites is how they enable teams to work independently and to continuously deploy to production. However, there is a lot to think about:

  • how do we make them resilient and fault-tolerant?
  • how can they scale elastically and cost-effectively?
  • how do they find each other and/or the message queues they need to communicate?
  • how can we monitor them and receive feedback from them to allow us to deliver better value?

Luckily Kubernetes handles a lot of these concerns for us.

Getting started with Kubernetes and cloud-native development can be daunting. So lets take it step-by-step in creating and deploying a microservice to a Kubernetes cluster and exposing it to the outside world.

We don’t want to be laden with the onerous tasks of creating and managing our own Kubernetes cluster. This is where GKE on Google Cloud comes in. It handles the creation and managing of Kubernetes clusters, so we don’t have to. Before we dive into building and deploying our microservice, lets back up and take a whistle-stop tour of what Kubernetes is, and the Kubernetes objects we will be creating to complete our task.

Kubernetes Primer

Kubernetes is an open-source platform that abstracts away the complexities of managing distributed systems. Being infrastructure agnostic, it can be run on a developer’s laptop, in private datacenters, or in the cloud. The key to this abstraction is Kubernetes’ declarative specification approach. We give Kubernetes declarations of the requirements of our distributed system. These can include:

  • the container image for each application/microservice and other components such as databases and message queues;
  • the images for the containers that make up a single deployable unit (called a Kubernetes Pod), e.g. a microservice
  • the number of copies of each deployable unit, called "replicas", that should always be up and running (called a Kubernetes Deployment)
  • the way in which copies of a deployable unit will be exposed and load balanced behind a single IP address
  • the configuration and secret management for each deployable unit.

These types of specifications can be declared once and Kubernetes can bring them to life anywhere. Kubernetes consumes these specifications through the Kubernetes API. It stores them as Kubernetes "objects" and does its damndest to make sure that the live running system always conforms to these specifications.

Fundamental Kubernetes Components and Objects

To get our microservice up and running and replicated on Kubernetes, we will need a quick intro into some of its fundamental components and building blocks. To make this more concrete, it helps to know what we are trying to build and deploy on a Kubernetes cluster. My kids are having loads of fun learning about dinosaurs these days. So what better to use as an example microservice then a dinosaur microservice. Our dinosaur-service will have one very simple REST endpoint to get back a list of dinosaurs. Each dinosaur in the list will have a name, a phonetic spelling of the name, its diet and its length. The dinosaur-service will be built as a docker image so that it can be easily pulled down from Docker Hub by the machines on our Kubernetes cluster.

The Kubernetes Cluster

A Kubernetes cluster can have one, but usually more, machines. Each machine is a Node. Some of these act as the cluster Control Plane. This is the brain of the cluster. It accepts and stores specifications about a desired distributed system to be run on the cluster and ensures that the system always conforms to these specifications. It also reports on the status of the system and of individual components in the system. The other nodes in the cluster are Worker Nodes. These are the nodes on which our applications actually run. There are usually multiple worker nodes. The control plane will get instances of our dinosaur-service up and running on the worker nodes. To do this though, it needs to know vital information like:

  • how many replicas of the dinosaur-service do we want?
  • what image does each worker node need in order to spin up our microservice containers?
  • how should the load be distributed across the replicas?
  • how will the replicas will be exposed to the outside world so that we can actually make requests to them and get back our dinosaurs?

All of these answers and more can be provided to the control plane as specifications via the cluster Kubernetes API. These are stored as Kubernetes objects.

Figure 1 shows an overview of the Kubernetes components that are fundamental to understanding how replicas of our dinosaur microservice will be deployed. It also shows instances of one of the Kubernetes objects that we will use – the Kubernetes Pod. There are other important Kubernetes objects at play here called a Kubernetes Deployment and a Kubernetes Service.These will be explained in due course.

Figure 1

As shown in Figure 1, we create specifications for the desired state of our system. These are posted to the Kubernetes API which is a component running in the control plane. They are then saved as Kubernetes objects to a data-store – etcd. Components called Controllers also run in the control plane. These look after interacting with other Kubernetes components – via the Kubernetes API – and with outside components such Cloud services in order to realise the desired state of our system. One of these controllers is the Kubernetes Scheduler. It processes our Kubernetes objects and interacts with a Kubernetes component – via the Kubernetes API – on each worker node called the Kubelet. The Kubelet, in turn, interacts with the container runtime (e.g. Docker, Containerd) on its worker node to create the desired container instances to run our applications. These are shown in Figure 1 as a "dinosaur-container" on each worker node. Naturally one would believe that a container is the smallest deployable unit in Kubernetes. However this is not the case. The Kubernetes Pod is the smallest deployable unit. Our specifications that we send to the Kubernetes API include Pod specifications. The Kubernetes API stores Pod objects in etcd and the Scheduler looks after instructing each Kubelet to instantiate them. This is shown in Figure 1 as "dinosaur-pod-1", "dinosaur-pod-2" and "dinosaur-pod-3".

The Kubernetes Pod

Containers such as Docker containers are essentially Linux processes. When we say an application is running "inside a container", it sounds like it is running inside a Virtual Machine (VM) on top of Linux. However, it is actually running as a Linux process. The trick is that, for all intents and purposes, the application "thinks" its running in its own isolated machine. Linux achieves this by carefully splitting out resources between each of these processes (each "container"). For the purpose of explaining how a Pod can be the smallest unit of deployment in Kubernetes, Linux "namespaces" are the most useful. Namespaces are a mechanism Linux uses to divide different types of resources between processes such that, from the process’ point of view, there has been no division of the resource. For example, it is this mechanism that allows two or more processes running on the same Linux OS to listen on one particular port e.g. 8080. From the point of view of each process, it is running on native Linux OS port 8080. However, in reality each 8080 port is virtual and belongs to the Network namespace of the process that is listening on it. There are different types of Linux namespaces, some of which are:

  • The PID namespaces – namespacing of process ids
  • The Network namespaces – namespacing of network stacks and ports etc.
  • The Mount namespaces – namespacing of mount points.
  • The Unix Time Sharing (UTS) namespaces – namespacing of hostnames etc.

Multiple processes can share the same namespace for each type of namespace. Further, two or more processes can share one or more types of namespaces and not share other types of namespaces. This is how a Pod can be the smallest deployable unit in Kubernetes. Containers that make up a particular pod share certain namespace types such as the Network namespace with other containers in the same pod. However, these containers don’t share some other types of namespaces. It means, for example that each Pod can have its own internal IP address. There can be multiple copies of an instance of a Pod spread out across multiple nodes. However, the containers that make up each Pod instance have to be completely deployed on one machine.

The Kubernetes Deployment

In order to provide redundancy, we deploy multiple copies, called "replicas" of our microservices. We spread out our replicas across nodes and, often in the cloud, across availability zones. So, if one node goes down, our replicas on other nodes have our backs. Managing these replicas across nodes and ensuring that the correct number are always running would be a very onerous task for one microservice. For many microservices, it becomes virtually impossible. Luckily, this is something we don’t have to think through. Kubernetes handles this for us. Like other Kubernetes objects, such as Pods, we simply provide Kubernetes a specification including the number of replicas of our microservice that should always be up and running. Kubernetes calls this type of specification a Deployment.

The Kubernetes Service

So, great, we can get our dinosaur-service Pods up and running and adhering to a Deployment object. Each pod has its own IP address. So once we expose our pods to the outside world via our Cloud load balancer, we should be good to go? Well, not really, if one of the Pods crashes or if the node it is running on goes down, Kubernetes, will create another Pod to replace it. As mentioned earlier, Kubernetes does its damndest to make sure that our microservice replicas adhere to their Deployment specification. The replacement Pod, most likely, has a completely different IP address and could be running on a different node. This poses a problem. How on earth can we keep track or where all our Pods are running at any moment in time? Luckily we don’t have to! Kubernetes does this by way of its Service objects. A Service is a specification of a set of Pods that we want to be exposed on a single IP address. It also includes other requirements such as how we want traffic to be load-balanced across the set of Pods that make up the Service.

So now we have an understanding of the Kubernetes components and objects that we will leverage to deploy replicas of our dinosaur-service and expose it via a single IP address. Let’s turn our attention to the dinosaur-service itself and get it implemented and packaged up into a docker image.

The Dinosaur Microservice

The microservice consists of one simple endpoint:

/dinosaurs

It returns a JSON response body containing a hard-coded list of dinosaurs:

[
  {
    "Name": "Coelophysis",
    "Pronunciation": "seel-OH-fie-sis",
    "LengthInMeters": 2,
    "Diet": "carnivorous"
  },
  {
    "Name": "Triceratops",
    "Pronunciation": "tri-SERRA-tops",
    "LengthInMeters": 9,
    "Diet": "herbivorous"
  },
  {
    "Name": "Tyrannosaurus",
    "Pronunciation": "tie-RAN-oh-sore-us",
    "LengthInMeters": 12,
    "Diet": "carnivorous"
  },
  {
    "Name": "Diplodocus",
    "Pronunciation": "DIP-low DOCK-us",
    "LengthInMeters": 26,
    "Diet": "herbivorous"
  },
  {
    "Name": "Panoplosaurus",
    "Pronunciation": "pan-op-loh-sore-us",
    "LengthInMeters": 7,
    "Diet": "herbivorous"
  }
]

This microservice can be implemented in one Go file, in this case, called dinosaur-service.go The contents are as follows:

dinosaur-service.go

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)


type Dinosaur struct {
	Name           string
	Pronunciation  string
	LengthInMeters int
	Diet           string
}

func main() {
	dinosaurs := []Dinosaur{
		{
			Name:           "Coelophysis",
			Pronunciation:  "seel-OH-fie-sis",
			LengthInMeters: 2,
			Diet:           "carnivorous",
		},
		{
			Name:           "Triceratops",
			Pronunciation:  "tri-SERRA-tops",
			LengthInMeters: 9,
			Diet:           "herbivorous",
		},
		{
			Name:           "Tyrannosaurus",
			Pronunciation:  "tie-RAN-oh-sore-us",
			LengthInMeters: 12,
			Diet:           "carnivorous",
		},
		{
			Name:           "Diplodocus",
			Pronunciation:  "DIP-low DOCK-us",
			LengthInMeters: 26,
			Diet:           "herbivorous",
		},
		{
			Name:           "Panoplosaurus",
			Pronunciation:  "pan-op-loh-sore-us",
			LengthInMeters: 7,
			Diet:           "herbivorous",
		},
	}

	http.HandleFunc("/dinosaurs", func(writer http.ResponseWriter, request *http.Request) {
		jsonResponse, err := json.Marshal(dinosaurs)
		if err != nil {
			log.Println("cannot serialize dinosaurs")
		}

		_, err = fmt.Fprintln(writer, string(jsonResponse))

		if err != nil {
			log.Println("Could not write dinosaurs to the response")
		}
	})

	log.Println("Dinosaur service starting on port 8084")
	err := http.ListenAndServe(":8084", nil)
	if err != nil {
		log.Fatal("dinosaur-service could not start")
		return
	}
}

This consists of a Dinosaur struct type to contain data for each of our dinosaurs. The main function simply creates a Go slice of Dinosaurs and sets up a web server to listen on port 8084, and a handler function for our /dinosours endpoint. The handler function simply serializes the dinosaurs slice to json and sends it back on the http response. The great thing about using Go is that this microservice can be fully implemented in one file and only using Go standard libraries i.e. without the need to include any third-party libraries.

With Go installed, we can build and run the dinosaur-service as follows:

go build dinosaur-service.go
go ./dinosaur-service

Then we can hit our /dinosaurs endpoint using curl, Postman or whatever our favourite http client is by hitting:

http://localhost:8084/dinosaurs
Accept: application/json

In order for our dinosaur-service to be deployable to a Kubernetes cluster, it will need to be packaged as acontainer image. We can do this with Docker by including the following Dockerfile in the same directory as our dinosaur-service.go file.

Dockerfile

FROM golang:1.16

COPY . .

EXPOSE 8084

RUN go build dinosaur-service.go

CMD ["./dinosaur-service"]

With this in place, the following command will build the docker image:

docker build . -t [DOCKER_USER_ON_Docker Hub]/dinosaur-service:latest

Because this image will later be retrieved from Docker Hub so that it can be used to create containers in our Kubernetes Pods, it needs to be made available on Docker Hub. The [DOCKER_HUB_USERNAME] needs to be replaced with the username for an account on Docker Hub. Also, this should be the username of the account currently logged into Docker Hub on our local machine.

To push the image built from the previous command to Docker Hub, we use:

docker push [DOCKER_HUB_USERNAME]/dinosaur-service
Again the [DOCKER_HUB_USERNAME] placeholder needs to be replaced with the username for a Docker Hub account.

Deploying the Dinosaur Microservice to Kubernetes

GKE is the Kubernetes offering on Google Cloud. It makes it straight forward to create Kubernetes clusters and to interact with them via the standard Kubernetes command line client, kubectl which can be downloaded here. A Google Cloud account is needed to use GKE. Disclaimer! Be careful though to thoroughly understand the costs involved before using GKE or any other offering on Google Cloud.

The gcloud command line client can be used to create a Kubernetes cluster on GKE(https://cloud.google.com/sdk/docs/install). With gcloud installed, we can log into our Google Cloud account with the following:

gcloud auth login

We can then set our current Google Cloud project that gcloud points to with:

gcloud config set project [PROJECT_NAME]
replacing [PROJECT_NAME] with a Google Cloud project. For those new to Google Cloud, it has good documentation explaining Google Cloud Projects here.

We can create a Kubernetes cluster on GKE with the following:

gcloud container clusters create [CLUSTER_NAME] --num-nodes 3
replacing [CLUSTER_NAME] with the name we would like to give to our cluster. The -num-nodes 3 argument specifies that we want 3 nodes in our cluster. With kubectl already installed, the above gcloud command will also configure kubectl to point to our newly created cluster. This can be confirmed by running the following:

kubectl config current-context

The number of nodes in our cluster can later be scaled back down to 0 nodes using:

gcloud container clusters resize [CLUSTER_NAME] --size 0

Now that our cluster is in place, we need to provide it with a specification for dinosaur-service deployments including the number of replicas we require. We do this through supplying the cluster with a Deployment object via the cluster Kubernetes API. The deployment object can be specified with a yaml file of a name of our choosing. Let’s call this dinosaur-deployment.yaml The contents of this file are as follows:

kind: Deployment
metadata:
  name: dinosaur-deployment
spec:
  selector:
    matchLabels:
      app: dinasour-service
  replicas: 3
  template:
    metadata:
      labels:
        app: dinasour-service
    spec:
      containers:
        - name: dinasour-service
          image: [DOCKER_HUB_USERNAME]/dinosaur-service
          ports:
            - containerPort: 8084

The placeholder in this file, [DOCKER_HUB_USERNAME], needs to be replaced with the Docker Hub username that we used earlier when we pushed our dinosaur-service image to Docker Hub. This Kubernetes object can be sent to the Kubernetes API with the following:

kubectl apply -f dinosaur-deployment.yaml

We can then check the status of our deployment using:

kubectl kubectl get deployments

At first this might show something like the following:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
dinosaur-deployment   0/3     3            2           26s

This means that no Pods are up and running yet. However, eventually, when we run the command again, it will show something like the following:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
dinosaur-deployment   3/3     3            3           27s

So now that we have our dinosaur-service Pods up and running, we can expose them to the outside world by giving the Kubernetes API a specification of a Kubernetes Service object. We will use a type of Kubernetes Service object called LoadBalancer. This, in turn will instruct our cluster Control Plane to set up a Google Cloud load balancer to route traffic to our cluster nodes. Also based on the Service object, Kubernetes sets up the IP-tables on each worker node via a Kubernetes component running each worker node called kube-proxy. This all ensures that each request to our dinosaur microservice reaches one of the dinosaur-service Pod replicas.

The Service object can be posted to the Kubernetes API by sending a yaml specification file. It can also be done through the following kubectl command:

kubectl expose deployment dinosaur-deployment --type=LoadBalancer --port 8084

We can get the details of this Kubernetes service with:

kubectl get svc

This will show something like:

NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
dinosaur-deployment   LoadBalancer   10.87.253.183        8084:31928/TCP   19s

Running the command again will eventually show the assigned external IP:

NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
dinosaur-deployment   LoadBalancer   10.87.253.183   35.198.77.68   8084:31928/TCP   94s

Based on the external IP that is shown above, we can now hit our /dinasours endpoint using Postman or any other http client of choice:

GET http://35.198.77.68:8084/dinosaurs
Accept: application/json

This returns our list of dinosaurs!

[
    {"Name":"Coelophysis","Pronunciation":"seel-OH-fie-sis","LengthInMeters":2,"Diet":"carnivorous"},
    {"Name":"Triceratops","Pronunciation":"tri-SERRA-tops","LengthInMeters":9,"Diet":"herbivorous"},
    {"Name":"Tyrannosaurus","Pronunciation":"tie-RAN-oh-sore-us","LengthInMeters":12,"Diet":"carnivorous"},
    {"Name":"Diplodocus","Pronunciation":"DIP-low DOCK-us","LengthInMeters":26,"Diet":"herbivorous"},
    {"Name":"Panoplosaurus","Pronunciation":"pan-op-loh-sore-us","LengthInMeters":7,"Diet":"herbivorous"}
]

Conclusion

I hope this blog has given a taster for what Kubernetes has to offer and how it can be leveraged for rapid delivery. Thanks for reading!

Model View Events with Action Replay — No Frameworks Allowed

(Originally published in December 2020 – moved here from another blog site that I had)

In a previous blog post I mused on playful creativity and innovation. I reflected on the software industry from what I have seen in my career so far. I talked about how companies can become overburdened with processes and bureaucracy. It gets to the point where getting ideas from our heads into working software in the hands of our users becomes a real chore. We can forget how to play and innovate becuase of the walls of process that get in our way.

Over the Christmas holidays, I got an itching for some playful creativity. To put it bluntly, I just wanted to fire up a text editor and knock out some code to build something! I looked into the world of endless possiblilities that was the blank text buffer in front of me. I thought to myself “Hello Javascript, my old friend, its been a while!” so lets build some small browser game. I’m also quite partial to event driven systems. I love capturing actions that can be assembled into a state at a later point. The game, Tik Tak Toe, came to mind. I also remembered being at a conference a few years ago and seeing a talk about one of the many Javascript frameworks out there. The speaker showed time-travel debugging and the ability to “replay” an application up to a certain point by replaying all the events up to that point. So I thought “sure lets make Tic Tak Toe with action replay!”. Then my kids can play it and replay games to see the moves that lead up to the win!

So, I was off to the races. I had a blank text buffer in front of me. I was sitting on my comfortable arm-chair with my coffee in hand. My kids were busy playing a game that they had just concocted from their imagination. By the way, one thing that has thought me a lot about playful innovation is seeing the games that my kids invent for themselves involving different characters and role-plays. It’s an amazing thing!

One might ask which framework did I use? How did I narrow it down from the multitude of Javascript frameworks out there that can manage events and state and show stuff on the screen? I’ve used many frameworks over the years both server-side and client-side. They’re handy. They help get stuff done without having to build, say, a web server from scratch. But, let’s face it, they can be a complete pain!Granted, small libraries are often needed but a fully fledged framework the likes of Angular, React or Spring Boot is generally not needed until a project reaches a certain size.

I wanted to show some boxes on the screen, allow the users to enter in Xs and Os and capture everything that happens so that it can be replayed later. Most of this could nearly be achieved in the time taken for NPM to download Angular or some other framework and for it to be all bootstrapped into a project. No, I was going commando on this one — an html file and, in the end, 2 Javascript files.

But surely I used NPM to download and use some Javascript testing framework and got it all hooked up with tasks to run my tests? Nah, I just wrote a test module in the same Javascript file as the code I was testing. Then I could run my tests by starting a node repl and loading in that file.
The main logic is in checking for a win or a game that has completed with no winner. I extracted this into a separate Javascript file and added tests for it in there too. A sippet of this is:

const tests = () => {
    let testsPassed;
    let gameboardWithNoCompletedRowsOrColumnsButAllCellsCompleted = [
        ['X','O', 'X'],
        ['O','O', 'X'],
        ['O','X', 'O']
    ]

    testsPassed = !winChecker.thereIsAWinner(gameboardWithNoCompletedRowsOrColumnsButAllCellsCompleted);
    console.log('Test thereIsAWinner gameboardWithNoCompletedRowsOrColumnsButAllCellsCompleted ' + testsPassed);

    testsPassed = winChecker.gameBoardIsFull(gameboardWithNoCompletedRowsOrColumnsButAllCellsCompleted);
    console.log('Test gameboardIsFull gameboardWithNoCompletedRowsOrColumnsButAllCellsCompleted ' + testsPassed);

    let gameboardWithCompletedRow0 = [
        ['X','X', 'X'],
        ['O','O', 'X'],
        ['O','X', 'O']
    ]

    ....
    ....
    ....

So going commando on this meant that I needed some simple way to dispatch and subscribe to all the events that correspond to actions on the game. For example, these include:

winner-determined-event
completed-with-no-winner-event
game-board-updated-event
turn-taken-event

with each event also holding data related to it e.g. the player that took their turn in the turn-taken-event. I took care of this in a module I called eventDispatcher as follows:

let eventDispatcher = function() {
    let replaying = false;
    let events = [];
    let subscribers = {};
    let dispatchEvent = (ev) => {
        if (!replaying) { 
            events.push(ev);
        }

        for (const sub of subscribers[ev.name]) {
            sub(ev);
        } 
    }

    let subscribe = (eventName, f) => {
        if(subscribers[eventName]) {
            subscribers[eventName].push(f);
        } else {
            subscribers[eventName] = [f];
        }
    }

    let replay = () => {
        replaying = true;
        let i = 1 
        for (const ev of events) {
            setTimeout(() => dispatchEvent(ev), 1000 * i);
            i++;
        }
        setTimeout(() => replaying = false, 1000 * events.length)
    }

    return {
        dispatch: dispatchEvent,
        subscribe: subscribe,
        replay: replay
    }
}();

This module also handles storing and replaying events. The storing and replaying of events should really be moved out to another appropriately named module called ‘eventStore’. But, come on, give me a break, I was having some fun! Subscribers are stored in a simple hash keyed by event names. The events themselves are stored by pushing them into an array. The replay mechanism can then process this array of events and dispatch each one to replay it. The dispatch mechanism simply calls the subscrbers — which are functions — for the event being dispatched.

Following a CQRS’ish (Command Query Responsibility Segregation) type design, the core of the game doesn’t store end-state but just stores the events. However, we need boxes with X’s and Os to be drawn on the screen, user clicks/taps to be accepted or the “Action Replay” button to be pressed, along with other data being displayed. For this, there is a view module which uses its own ongoing record of the state which it tracks based on events that it responds to from the eventDispatcher. This is the “query” part of this CQRS’ish design. A snippet from the start of the view module is below. It subscribes to the various events of the system and updates its own view model accordingly and re-draws the screen.

let view = function() {
    let viewModel = {
        currentPlayer: 'X',
        winner: null
    };

    let gameBoard = new Array(3).fill(new Array(3).fill(''));

    eventDispatcher.subscribe('game-board-updated-event', gameBoardUpdatedEvent => {
        viewModel.currentPlayer = gameBoardUpdatedEvent.nextPlayer;
        gameBoard = gameBoardUpdatedEvent.gameboard;
        drawBoard(gameBoard);
    });

    eventDispatcher.subscribe('winner-determined-event', winnerDeterminedEvent => {
        viewModel.winner = winnerDeterminedEvent.winner;
        gameBoard = winnerDeterminedEvent.gameboard;
        drawBoard(gameBoard);
    });

    eventDispatcher.subscribe('completed-with-no-winner-event', completedWithNoWinnerEvent => {
        viewModel.winner = 'NO_WINNER';
        gameBoard = completedWithNoWinnerEvent.gameboard;
        drawBoard(gameBoard);
    });

    document.getElementById('replay-button').addEventListener('click', () => {
        viewModel = {
            currentPlayer: 'X',
            winner: null
        };

        let gameboard = new Array(3).fill(new Array(3).fill(''));
        drawBoard(gameboard);
        eventDispatcher.replay();
    });

    const drawBoard = (gameBoard) => {
        let heading = '';
        if (viewModel.winner === 'NO_WINNER') {
            heading = 'Game completed with no winner';
        } else if (viewModel.winner) {
            heading = viewModel.winner + ' has won!!'  
        } else {
            heading = 'Current Player is: ' + viewModel.currentPlayer;
        }
        let headingView = document.getElementById('heading');
        headingView.textContent = heading;

        let gameBoardView = document.getElementById('gameBoard');
    ...
    ...
    ...

And, yes, that gameBoard variable should be part of the viewModel object — something to be refactored a bit. The rest of the view module just contains code to draw the rows and the cells. It also attaches a click handler to each cell:

  cellView.addEventListener('click', 
        () => gameEngine.handleEvent(
            {
                name: 'turn-taken-event',
                player: viewModel.currentPlayer,
                row: rIndex,
                col: cIndex, 
                gameboard: gameBoard
            }));

The main engine for the game decides what to do for each turn-taken-event. It’s in a module called gameEngine. It checks to see if the game has been won or completed and dispatches events accordingly. It’s main logic is below:

let handleTurnTaken = (turnTakenEvent) => {
    if(turnTakenEvent.gameboard[turnTakenEvent.row][turnTakenEvent.col] !== 'X' && turnTakenEvent.gameboard[turnTakenEvent.row][turnTakenEvent.col] !== 'O') {

        console.log('Player ' + turnTakenEvent.player + ' has taken a turn');
        let nextPlayer = turnTakenEvent.player === 'O' ? 'X' : 'O';
        let nextGameBoard = turnTakenEvent.gameboard.map(
            (row, rIndex) => {
                return row.map((col, cIndex) => {
                    return rIndex === turnTakenEvent.row && cIndex === turnTakenEvent.col ?
                        turnTakenEvent.player : col 
                });
            }
        ) 

        if (winChecker.thereIsAWinner(nextGameBoard)) {
            eventDispatcher.dispatch({
                name: 'winner-determined-event',
                winner: turnTakenEvent.player,
                gameboard: nextGameBoard
            });
        }
        else if (winChecker.gameBoardIsFull(nextGameBoard)){
            eventDispatcher.dispatch({
                name: 'completed-with-no-winner-event',
                gameboard: nextGameBoard
            });
        } else {
            eventDispatcher.dispatch({
                name: 'game-board-updated-event',
                nextPlayer: nextPlayer,
                gameboard: nextGameBoard 
            });
        }
    }
}

The full code for the game is just 3 files and can be found on my github: https://github.com/priort/tic-tak-toe-with-action-replay
If you clone the repo and open index.html in a browser, you should be good to go. I’ve only tried it out using the latest version of Firefox so I don’t know if I have full cross-browser support just yet!

It was a lot of fun knocking this out. When I clicked the “Action Replay” button and saw all the events being replayed a second apart, I got that thrill that I first got when I wrote a simple program in Pascal back in my college days to render lines on a screen at increasing angles. I get even more of a thrill when I play the Tic Tak Toe game with my kids and watch as they hit “Action Replay” to see all the moves of each game. Creating something from nothing and then seeing or hearing about users’ joy in using that creation is one of the main reasons I love programming. It was nice to rekindle the joy of programming again over the holidays!

Running a “Copy Cat” Server in AWS

“Stop copying me!!” is a phrase that I hear a lot from the back of the car when my 3 kids are back there. The age old tried and tested way to really annoy somebody is to repeatedly repeat back everything they say. Children learn how effective this is from an early age. It’s especially powerful when the child that is being copied is already having a tantrum about something else!

Let’s get a CopyCatServer up on AWS in a few simple steps.

WARNING: This is a simple tutorial to show the ease of getting a simple Java process to run on AWS and to connect to it. Security controls such as Network Access Control Lists, Security Groups etc. are left very open. On a proper production or even test system, access should be properly controlled and limited using IAM users/groups, Subnets, NACLs, Route Tables, Security Groups etc.

First of all we need an AWS account. An account can be created at https://aws.amazon.com and it offers percentage usage of some services for free for about a year (be careful and check though before using any services!).
When signing up, you will have to go through a few steps of getting a confirmation code sent to your phone etc. but the process doesn’t take too long. When last I did this for a personal account, it took only a few minutes.

Once you have your AWS account set up and are signed into the AWS Console, you will see something like this:

EC2 may not be in “recently visited services”. If not, we can search for it and click into it and you should see something like this:

Click on “Launch instance” and you’ll see something like this.

This is a list of Amazon Machine Images (AMI). An AMI is analogous to a Docker image and is a specification for a virtual machine — i.e. the EC2 instance (Elastic Compute instance) — from which instances can be created and started. The AMIs that are eligable for free tier are marked as such. Lets select the second one down (which is marked as free tier) — Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type — ami-0fa94ecf2fef3420b. After selecting this you should see a screen to choose and “Instance Type”.

An instance type is a specification of the computing resources that will be associated with the EC2 instance that we spin up e.g. RAM, CPUs EBS (Elastic Block Storage — similar to hard disk) etc. Lets choose the one that is selected in the above image that is eligable for the fee tier. Click “Next: Configure Instance Details” and you should see a screen similar to this:

This screen allows us to configure more details such as which VPC (Virtual Private Cloud), Subnet etc. that the EC2 instance will be associated with. We’ll just go with defaults here. A very high level overview is that a VPC is a virtual private network that you own within AWS. A subnet is a section of the network inside this VPC. A VPC is associated with a region e.g. eu-west-1 for Ireland. Inside a region, there are availability zones and inside an availability zone there is a data-centre with the physical hardware on which services (e.g. like our EC2 instance) are running. So our EC2 instance will be running in a data-centre which will be in a subnet which will be in an availability zone (subnets can’t span availability zones) which will be in a region. Ideally, we would spin up multiple replicas of the same EC2 instance in different availability zones and have our applications replicated across them for availability and fault tolerance. However, as this is just a quick demo, we will spin up one EC2 instance. This is done by clicking “Review and Launch” which brings us to a screen similar to this:

Clicking launch brings us to:

This allows us to create a key pair and download a .pem file which we can use later to ssh into the EC2 instance.

We can give the key pair a name and download the pem file. Then click “Launch Instances” and you should see a screen similar to this:

At the bottom of the page, we can click “View Instances”. After a short while (minute or two), if we click this we should see the EC2 instance running:

If we click on “Connect”, we get instructions on how to use the .pem file we downloaded earlier to ssh to the EC2 instance.

I have pasted these instructions below for convenience:

To access your instance:
Open an SSH client. (find out how to connect using PuTTY)
Locate your private key file (tom-demo.pem). The wizard automatically detects the key you used to launch the instance.
Your key must not be publicly viewable for SSH to work. Use this command if needed:
chmod 400 tom-demo.pem
Connect to your instance using its Public DNS:
ec2–99–79–70–139.ca-central-1.compute.amazonaws.com
Example:
ssh -i “tom-demo.pem” ec2-user@ec2–99–79–70–139.ca-central-1.compute.amazonaws.com
Please note that in most cases the username above will be correct, however please ensure that you read your AMI usage instructions to ensure that the AMI owner has not changed the default AMI username.

After ssh’ing into the EC2 instance, we will see something like this in our terminal:

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
4 package(s) needed for security, out of 10 available
Run “sudo yum update” to apply all updates.
[ec2-user@ip-172–31–15–122 ~]$

Run

sudo yum update

Running the CopyCatServer on EC2

To compile and run some java code on our EC2 instance, we need JDK 11. We can use Amazon Coretto JDK which we can download onto the instance (while ssh’d in) as follows:

wget https://corretto.aws/downloads/latest/amazon-corretto-11-x64-linux-jdk.rpm

After installation, we can confirm that we have the JDK installed by running:

java -version

This should show something like:

openjdk version “11.0.6” 2020–01–14 LTS OpenJDK Runtime Environment Corretto-11.0.6.10.1 (build 11.0.6+10-LTS) OpenJDK 64-Bit Server VM Corretto-11.0.6.10.1 (build 11.0.6+10-LTS, mixed mode)

Now we need some code to run. We can open the VIM text editor and create a java file by running

vim CopyCatServer.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CopyCatServer {
public static void main(String[] args) throws IOException {
        try (ServerSocket sc = new ServerSocket(8080);) {
            while (true) {
                try (Socket socket = sc.accept();
                        InputStream is = socket.getInputStream();
                        OutputStream os = socket.getOutputStream()) {
is.transferTo(os);
                } catch (Exception e) {
                    throw e;
                }
            }
        } catch (Exception e) {
            throw e;
        }
    }
}

If using iterm2 (https://www.iterm2.com/), this can be simply copied and pasted in. Once it’s entered, leave insert mode by pressing esc and save the file and close vim by typing :wq

Now we can compile our code running:

javac CopyCatServer.java

And we can get our CopyCatServer running by running:

java CopyCatServer

Once it is running, open another terminal tab/window from your local machine and let’s telnet to our java server.
If you don’t have telnet installed, it can be installed on mac with homebrew.

brew install telnet

telnet can also be installed as easily on linux and windows systems. A quick internet search will reveal multiple quick tutorials on how to get it set up.

With telnet, we can now telnet to our CopyCatServer which is running on our EC2 instance using the public IP address (this will vary for your instance but it is shown earlier where to get this) by running:

telnet 99.79.70.139 8080

Unfortunately, you will find that this will just hang and timeout eventually.

Trying 99.79.70.139...

Allowing Access to the CopyCatServer

The reason that telnet just hangs is because, no traffic is allowed to access port 8080 on our EC2 instance. There is a security group that gets associated with it that allows traffic for ssh but not for port 8080 which is the port that our CopyCatServer is running on.
We can fix this by going back the the EC2 management screen we had before and scrolling down to the details to Security Group:

Click on “launch-wizard-1”. This brings us the the security group associated with our EC2 instance. We can look at the inbound rules from here:

Click Edit to add a new rule and we can add a rule to allow traffic to port 8080 as follows:

Click save and you will see that the rule is added:

No we can go back to the terminal and telnet from our local machine again:

➜ ~ telnet 99.79.70.139 8080
Trying 99.79.70.139...
Connected to ec2-99-79-70-139.ca-central-1.compute.amazonaws.com. Escape character is '^]'.

This time we will see that telnet connects. Now anything we type into the telnet session will be simply copied back to us from our CopyCatServer just like an annoying person that copies everything we say!

Before we finish, be sure to shut down your EC2 instance. We can go back to the EC2 dashboard:

Then go into “Running instances”:

Then the instance can be stopped as follows:

Keeping jazz bands valid with Scala

Algebraic data types(ADTs) sound pretty esoteric but they are very practical and they are a key feature of typed functional programming languages like Scala. They allow us to model our domains in a type safe way and to delegate some of the testing burden to the compiler.

Two of the essential words to understand in the context of ADTs are the words “and” and “or”. In fact I believe that even just knowing the meaning of these two words in the context of domain modelling can take you a long way in modelling complex domains. When the domain conforms strictly to the words “and” and “or” we can make it impossible for illegal states in our application to be created (unless one were to get pretty hacky with reflection! — which would be caught in a pull request as quickly as if one decided to delete important tests). That being said, there are types that can be modelled that have one implementation type or that are made up of one implementation type.

So this “and” and “or” business. If we have a “thing”, we can use the words “AND” or “OR” to describe that thing. If we use the word “and”, we say “this thing is made up of this AND that AND that AND…” and so on. If we use the word “or”, we say “this thing is either this OR that OR…” and so on. In the case where something is made up of one implementation type, it is still similar to other “AND” types but it just happens to have one thing so the “AND” word isn’t needed. The same is true for “OR” types. There can be an “OR” type where there is only one implementation type to choose from.

So lets look at an example. We are going to model a domain of Jazz bands. One common way for a jazz band be constructed is by combining a piano player AND a bass player AND a drummer. This is commonly called a piano trio. Because we are combining musicians to form the piano trio, we say that the piano trio is an “AND” type or, to be more formal about it, a product type.

Here is what defining the piano trio looks like in Scala:

sealed trait JazzBand

final case class PianoTrio(pianoPlayer: PianoPlayer,
                           bassPlayer: BassPlayer,
                           drummer: Drummer) extends JazzBand

Instances of this class are immutable and have to be made up of a piano player, a bass player and a drummer. I will get to the types PianoPlayer, BassPlayer and Drummer shortly. The case class is final so that it can’t be overridden.

As much as I love listening to swingin’ piano trios, there’s a lot more types of jazz bands out there. For our application, we only want to make it possible to create piano trios, quartets and quintets. These are defined with the following product types.

final case class Quartet( trumpetPlayer: TrumpetPlayer, 
                          pianoPlayer: PianoPlayer, 
                          bassPlayer: BassPlayer, 
                          drummer: Drummer)

final case class Quintet( saxPlayer: SaxPlayer, 
                          trumpetPlayer: TrumpetPlayer, 
                          pianoPlayer: PianoPlayer, 
                          bassPlayer: BassPlayer, 
                          drummer: Drummer)

So our application is only going to handle piano trios, quartets and quintets — but how do we enforce that? How can we make sure that whenever we pass a jazz band to a function that it will only be either a piano trio OR a quartet OR a quintet. The answer is in the “OR” word — more commonly referred to as the sum type. This is where we specify that an instance (also called a data term value) is either this OR that OR that etc.

To define a sum type in scala, we can use a sealed trait and then define implementations of that trait in the same scala file. The sealed keyword ensures that no other instances of the trait can be created outside of the file and it allows the compiler to check for exhaustive pattern matching as I will show shortly — so, when pattern matching on implementations of the trait, the compiler will let us know if we have missed a possible type of jazz band (one or more implementations of the trait).

So we define our Jazz band sum type in scala as follows:

sealed trait JazzBand 

final case class PianoTrio( pianoPlayer: PianoPlayer, 
                            bassPlayer: BassPlayer, 
                            drummer: Drummer) extends JazzBand 

final case class Quintet( saxPlayer: SaxPlayer, 
                          trumpetPlayer: TrumpetPlayer, 
                          pianoPlayer: PianoPlayer, 
                          bassPlayer: BassPlayer, 
                          drummer: Drummer) extends JazzBand 


final case class Quartet( trumpetPlayer: TrumpetPlayer, 
                          pianoPlayer: PianoPlayer, 
                          bassPlayer: BassPlayer, 
                          drummer: Drummer) extends JazzBand

When creating these jazz bands, we want to capture the name of each musician in the band. However, if our case classes just took strings as parameters, we would lose a lot of type safety. If, for example, the drummer parameter was a string to represent a drummer’s name and not a Drummer type, we could easily just pass in the name of a bass player instead. A piano trio with two bass players instead of a bass player and a drummer would be pretty interesting but it wouldn’t be what our application expects — we don’t want this illegal state to be possible.

So the first step in preventing an illegal state like this is to create types for are musicians that each contain the musician’s name. Each one of these types is actually a product type but it only contains one thing — a string to represent the name. So there isn’t really an “AND” here but a type with one property is still called a product type. It is also possible to have a sum type where there isn’t an “OR” describing it. This is when there is only one choice. We could, for example, create a trait called PianoPlayerT and one case class implementing it — so this would be a single case sum type or single case union:

sealed trait PianoPlayerT 
case class PianoPlayer(name: String) extends PianoPlayerT

However, to keep things a bit simpler, lets just define a separate product type as a case class for each musician as follows:

case class PianoPlayer(name: String) 
case class SaxPlayer(name: String) 
case class TrumpetPlayer(name: String) 
case class BassPlayer(name: String) 
case class Drummer(name: String)

So this is great — instead of just passing strings around for the musician names, we have our nice types to pass around so we don’t mix up a bass player with a drummer……or do we really have this guarantee? who is to stop someone simply creating a drummer by passing a string for the name of someone who isn’t a drummer?

Drummer("Paul Chambers")

Paul Chambers was an amazing bass player — a stall-worth of hard-bop bands of the 50s and 60s — but now we’ve just made him a drummer!

In order to prevent this, we need to make it only possible to create a BassPlayer inside our tightly controlled Domain module. We do this with a combination of making the constructor private and exposing a “smart constructor”. This smart constructor returns an Option type which will be the Drummer wrapped in the Some Option case if the name we pass in is that of a drummer OR it will be the None Option case if we pass in a name of someone who is not a Drummer (notice the use of “OR” back there — Option is a sum type with two choices).

object Domain { 
 
  case class Drummer private (name:String) { 
      def copy() = Drummer(name) 
  } 
 
  object Drummer { 
    def apply(name:String) = 
      List("Max Roache", "Tony Williams", "Elvin Jones").find(_ == name).map(new Drummer(_)) 
  } 
}

The Drummer type is defined inside the Domain module and its constructor is private. So from outside the Domain module, an instance of the Drummer can’t be created with

new Drummer("Tom")

However in scala an apply function can be defined in a companion object to the type that you have defined. Scala creates this under the hood but you can create your own by defining it in the companion object (an object/module with the same name as the type you defined). The default scala implementation of it allows you to create a Drummer instance by simply calling:

Drummer("tom")

However we have defined our own in the Drummer companion object and defined it as a “smart constructor” which will validate that the name you give it is actually the name of a drummer — in our case, we just check if the name is in a list. If it is, it will return the created Drummer wrapped in the Some Option case or if the name is not on the list, it will return the None Option case.

Scala case classes also have a copy method though. So, in order to prevent someone simply copying a drummer instance and changing the name of the drummer, we have overridden the copy method of the Drummer case class to simply return a straight immutable copy of the Drummer.

From a different module, let’s try and create two drummers — one valid and the other invalid.

object Actions { 
  import Domain._ 
 
  val drummer = Drummer("Max Roache") 
  val invalidDrummer = Drummer("Paul Chambers") 
}

drummer evaluates to

scala> res0: Option[MISU.Domain.Drummer] = Some(Drummer(Max Roache))

and invalidDrummer evaluates to

scala> res1: Option[MISU.Domain.Drummer] = None

So, from outside the Domain module, we can’t create an invalid Drummer.

We can extend the Domain module to include our other musician types along with their smart constructors. The full domain with these and the JazzBand ADT is shown below:

object Domain { 
 
  sealed trait JazzBand 
 
  final case class PianoTrio(pianoPlayer: PianoPlayer, 
                             bassPlayer: BassPlayer, 
                             drummer: Drummer) extends JazzBand 
 
  final case class Quintet(saxPlayer: SaxPlayer, 
                           trumpetPlayer: TrumpetPlayer, 
                           pianoPlayer: PianoPlayer, 
                           bassPlayer: BassPlayer, 
                           drummer: Drummer) extends JazzBand 
 
 
  final case class Quartet(trumpetPlayer: TrumpetPlayer, 
                           pianoPlayer: PianoPlayer, 
                           bassPlayer: BassPlayer, 
                           drummer: Drummer) extends JazzBand 
 
  case class TrumpetPlayer private(name: String) { 
    def copy() = TrumpetPlayer(name: String) 
  } 
 
  object TrumpetPlayer { 
    def apply(name: String): Option[TrumpetPlayer] = 
      List("Miles Davis", "Freddie Hubbard").find(_ == name).map(new TrumpetPlayer(_)) 
  } 
 
  case class SaxPlayer private(name: String) { 
    def copy() = SaxPlayer(name: String) 
  } 
 
  object SaxPlayer { 
    def apply(name: String): Option[SaxPlayer] = 
      List("John Coltrane", "Charile Parker").find(_ == name).map(new SaxPlayer(_)) 
  } 
 
  case class PianoPlayer private(name: String) { 
    def copy() = PianoPlayer(name: String) 
  } 
 
  object PianoPlayer { 
    def apply(name: String): Option[PianoPlayer] = 
      List("Bill Evans", "Herbie Hancock").find(_ == name).map(new PianoPlayer(_)) 
  } 
 
  case class BassPlayer private(name: String) { 
    def copy() = BassPlayer(name) 
  } 
 
  object BassPlayer { 
    def apply(name: String): Option[BassPlayer] = 
      List("Paul Chambers", "Dave Holland").find(_ == name).map(new BassPlayer(_)) 
  } 
 
  case class Drummer private(name: String) { 
    def copy() = Drummer(name) 
  } 
 
  object Drummer { 
    def apply(name: String): Option[Drummer] = 
      List("Max Roache", "Tony Williams", "Elvin Jones").find(_ == name).map(new Drummer(_)) 
  } 
}

Now if we want to combine name strings to form one of our jazz band types, we can chain together the creation of each musician using flatMap and map on the Option type. Calling flatMap on an option allows you to pass in a function. If the Option is the Some case, the function will be evaluated on the value that is wrapped in the Some case and it will return another Option — so now we can potentially have a Some wrapping a Some but flatMap will flatten this down so that there is only one Some wrapper. In the case where the original Option is None, then the passed in function is not evaluated and None is returned from flatMap. So, the first time we encounter a smart constructor for a musician that returns None, the chain of flatMaps is essentially short circuited to return None.

This is shown in the Scala code below:

def createPianoTrio(pianoPlayerName:String, bassPlayerName:String, drummerName:String): Option[PianoTrio] = 
  PianoPlayer(pianoPlayerName) 
    .flatMap(validPianoPlayer => BassPlayer(bassPlayerName) 
      .flatMap(validBassPlayer => Drummer(drummerName) 
        .map( validDrummer => PianoTrio(validPianoPlayer, validBassPlayer, validDrummer))))

This is can be made much simpler using a for comprehension in scala which will do the above for us under the hood but it gives a much cleaner syntax:

def createPianoTrio(pianoPlayerName:String, bassPlayerName:String, drummerName:String): Option[PianoTrio] = 
    for { 
      pp <- PianoPlayer(pianoPlayerName) 
      bp <- BassPlayer(bassPlayerName) 
      d <- Drummer(drummerName) 
    } yield PianoTrio(pp, bp, d)

Evaluating a call to this function below demonstrates the case where Some containing a the piano trio is returned when all names correspond to correct musicians and when None is returned if one of the musician names is not valid.

scala> import MISU.Domain._ 
createPianoTrio("", "", "") 
import MISU.Domain._ 
scala> res0: Option[MISU.Domain.PianoTrio] = None 
scala> import MISU.Domain._ 
createPianoTrio("Bill Evans", "Paul Chambers", "Max Roache") 
scala> res1: Option[MISU.Domain.PianoTrio] = Some(PianoTrio(PianoPlayer(Bill Evans),BassPlayer(Paul Chambers),Drummer(Max Roache)))

Its worth noting that, even though the constructors for the musicians (PianoPlayer, TrumpetPlayer etc.) are all private, it is still possible to pattern match on these constructors from outside the Domain module.

object Actions { 
  import MISU.Domain._ 
 
 
  def getBandLeaderName(jazzBand: JazzBand) = 
    jazzBand match { 
      case PianoTrio(PianoPlayer(pianoPlayerName), _, _) => pianoPlayerName 
      case Quartet(TrumpetPlayer(trumpetPlayerName), _,  _, _) => trumpetPlayerName 
      case Quintet(SaxPlayer(saxPlayerName), _, _, _, _) => saxPlayerName 
    } 
}

So now our application can pass around jazz bands safe in the knowledge that, when it gets it encounters an instance of JazzBand type, that instance has to be a PianoTrio, OR a Quartet OR a Quintet and it has to contain the types of musicians that the application expects for each of these bands.

This lets the compiler check things that we would otherwise have to write extra tests for and makes our code nice and robust — no chance of any strange piano trios with two bass players!

Dip your toes in some Haskell, the water is warm here!

So, Haskell sounds quite daunting right? All this talk of monads and functors when all you want to do is just try out some simple problems and see what it’s like to play around with this language! Monads and functors are actually not that scary once you get into functional programming but, for today, I won’t mention them again. So, let’s dip our toes in and see what it’s like.

I often hear newcomers to functional programming sighing to themselves about how difficult it is to retrain their brains to read and write code in this paradigm that is new to them. Don’t get me wrong, the sighing is understandable especially when we have spent years wrapped in our imperative, object oriented comfort blankets with for loops and mutable variables. The sighing is usually witnessed when watching an experienced OO programmer getting to grips with a new codebase written in a functional language and seeing something like this:

isPalindrome (x1 : xs)  = x1 == last xs && isPalindrome (init xs)

Yep, there is a bit in there especially when you may be more used to the imperative way of writing such an algorithm.

public class Palindrome {

    public static boolean isPalindrome(String str) {

        int strEndIndex = str.length() - 1;

        for(int i = strEndIndex; i > (strEndIndex/2); i--)
           if(str.charAt(i) != str.charAt(strEndIndex - i))
               return false;

       return true;
   }
}

Ah, that’s better. Our old reliable Java for loop with our mutable counter. Let me take you on a small journey to give you a taster for Haskell. We’ll go from Haskell code that actually looks quite similar to that imperative Java code and gradually work towards that previous Haskell code snippet. We’ll use the good old palindrome kata to take us on that journey. The palindrome kata is a task to write a function which checks if a String is the same going from left to right as it is going right to left. Yes, I know, most languages have a library function, reverse, meaning we can simply reverse the String and compare with the original. But lets suspend our disbelief for a while so I can take you on a journey!

Quick setup to follow along

To start dipping your toes in, why not follow along with the Haskell examples in the rest of this post. Haskell quick installers for most types of OS can be found here:

After that, all you need is a text editor and your terminal.

Make a new file called DipYourToesIn.hs and add the declaration for the Haskell module where we will put our functions:

module DipYourToesIn where

Now crank up your favourite command line terminal and go to the directory where you created that file.

Now, simply type: ghci and this will start up the Haskell compiler in interactive mode allowing you to enter and evaluate Haskell expressions. We will use it to simply load and compile our DipYourToesIn.hs Haskell source so that we can call our Haskell functions as we create them to see it them working.

ghci> :l DipYourToesIn.hs 
[1 of 1] Compiling DipYourToesIn ( DipYourToesIn.hs, interpreted ) Ok, modules loaded: DipYourToesIn. 
ghci> isPalindromeWithRecursion "hello" 
False 
ghci> isPalindromeWithRecursion "racecar" 
True 
ghci>

The Haskell source can be loaded and compiled using :l DipYourToesIn.hs

Once it is compiled, we can now execute functions within that file by simply calling them.

In the next section, we will be creating the function, isPalindromeWithRecursion, but the above just shows how quick it is to start interacting with Haskell code as you write it. In saying that though, this shouldn’t be a substitution for tests. I would still write tests first, and use this interactive approach to experiment with the code that I am writing to make the tests pass.

Imperative Looking Haskell — looks can be deceiving!

So, lets’ start off with some Haskell that models the above Java solution quite closely.

isPalindromeWithRecursion :: String -> Bool
isPalindromeWithRecursion str =
 loop strEndIndex where

 strEndIndex = length str - 1
 loop i =
   if (i <= (div strEndIndex 2)) then True
   else if (str !! i) /= str !! (strEndIndex - i) then False
   else loop (i - 1)

A note about function type signatures

Line 3 in the above example contains the type signature for our function right after the :: after the function name. This specifies that the function takes a String parameter and returns a Bool (a boolean). Parameters and return types are separated by the -> symbol. If there are multiple parameters, they are all separated by -> with the last type being the return type. This is because every function in Haskell is actually a one parameter function. So, when we see a multiple parameter type signature, under the hood we have a series of outer to inner functions — a concept called currying. It is not important to know all about that just yet though if you are just dipping your toes in.

In Haskell there are no mutable variables or for loops but that doesn’t stop us from writing code that looks almost like the imperative Java solution. We define an inner loop function which is called recursively acting like our for loop.

  • On line 5, the where syntax there is a way to give bindings to local values or functions — essentially creating local immutable variables.
  • On line 8, we define the loop function which takes an immutable counter, i, meaning that the counter is “updated” by passing a new version of the counter value into the loop function every time it is called.
  • On line 5, the loop function is called with an initial counter, i, argument being the last index of the String — just like we initialised i in the Java solution.
  • The algorithm remains the same. We check the first and last characters. If they are equal, we continue checking the next 2 inner characters and so on. If we find two that are not equal, we can return False straight away and if we get down to only one or no characters remaining, we know we have a palindrome and return True.
  • This divides the end index of the String by 2 so that we can tell we have gotten to a point of 1 or 0 characters left.
(div strEndIndex 2)

This gets the i’th element of a list, in this case, the i’th character in the String str.

(str !! i)

This is how we compare a character going right to left with its corresponding character going left to right. /= meaning not equal to

(str !! i) /= str !! (strEndIndex — i)

As shown previously, we can load this into our GHCI and look at it in action.

ghci> :l DipYourToesIn.hs [1 of 1] 
Compiling DipYourToesIn ( DipYourToesIn.hs, interpreted ) 
Ok, modules loaded: DipYourToesIn. 
ghci> isPalindromeWithRecursion "hello" 
False 
ghci> isPalindromeWithRecursion "racecar" 
True 
ghci>

Guard those if then elses!

isPalindromeWithRecursionAndGuard :: String -> Bool
isPalindromeWithRecursionAndGuard str =
 loop strEndIndex where

   strEndIndex = length str - 1
   loop i
     | i <= (div strEndIndex 2) = True
     | (str !! i) /= str !! (strEndIndex - i) = False
     | otherwise = loop (i - 1)

Nothing has changed in our underlying algorithm. Also, not much has changed in the code but it is easier to read right? We still have the same set of conditions and corresponding expressions, one of which will be evaluated.

| i <= (div strEndIndex 2)
| (str !! i) /= str !! (strEndIndex - i)

and

| otherwise =

otherwise is the default case and its corresponding expression will be evaluated if none of the other boolean expressions have evaluated to True.

A bit about Haskell lists

A List in Haskell is a recursive data structure. A List can be empty and is simply represented as

[]

A List can also be non empty and, in this case, it is represented as a cell containing a value and a link to another List which itself can be empty or contain another cell with a value and a link to a further List. This recursive structure continues until we get an empty list.

The cons function in Haskell, represented with a

:

is used to create one of these list cells by joining a value together with an existing List which may or may not be empty. Using this function, we can create the List in the above diagram as follows:

1 : 2 : 3 : []

Haskell has a nicer way though with syntactic sugar allowing us to do the same thing with this bit of code:

[1, 2, 3]

A couple of List operations to help the palindrome cause

Haskell like other functional programming languages has very nice high level operations that we can use on lists. Lets try a couple with GHCI.

First lets create a List and bind it to a name, l. This is done in GHCI using the let keyword but, in Haskell source code, the let isn’t required.

ghci> let l = [1,2,3,4,5] 
ghci> l 
[1,2,3,4,5] 
ghci>

We’re going to need to be able to get the last element in the list so we can compare it to the first element. This is no problem though, we can simply use a function called last.

ghci> last l 
5 
ghci>

We will also need to get the first element in a list. We will be doing this later by refining the code with pattern matching. However, just for completeness, we do this by using a function called head.

ghci> head l 
1 
ghci>

Be careful though, calling head on an empty list will give an exception.

ghci> head [] *** 
Exception: Prelude.head: empty list 
ghci>

For reasons that will become apparent soon, we will also need to use a handy function called init. This creates a new list from everything in an existing list except the last value.

ghci> init l 
[1,2,3,4] 
ghci>

Polishing it off with pattern matching

Pattern matching is a concept very prevalent in functional programming. It allows you specify patterns to match against a data structure, its value type and by the structure and values of its elements. This allows the data structure to be deconstructed and parts of it extracted.

Function arguments are one place where pattern matching can be used very effectively. This is done in Haskell by redefining the function multiple times to deal with the different possible argument value patterns that can be passed in when the function is called.

isPalindrome :: String -> Bool
isPalindrome [] = True
isPalindrome (x : []) = True
isPalindrome (x1 : x2 : []) = x1 == x2
isPalindrome (x1 : xs)  = x1 == last xs && isPalindrome (init xs)
  • The final example above uses a mixture of pattern matching and the functions we saw earlier that we can use on lists. A String is a list of characters so we can pattern match on a list argument to the isPalindrome function.
  • Line 54 matches on the empty list. So, if an empty list is passed as an argument to the isPalindrome function, we return True straight away.
  • Line 55 pattern matches on a list with one element. We use the cons function, : , to match on a list structure which contains one cons cell with a value and a link to an empty list. The value of the cons cell can be anything and is bound to the value, x, in the pattern. So, with this pattern, we are checking if the list has only one element and, if it does, we can again return True.
  • Line 56 is pattern matching to check if the list argument has only 2 values. We again use the cons function to match against the list structure that we are looking for. We can also bind the first 2 values of the list to the names, x1 and x2. This allows us to check these for equality to determine if the 2 element list is a palindrome.
  • Finally, on line 57, we are back to that piece of code that is likely to make an experienced imperative programmer sigh when trying to get to grips with a functional programming codebase. When pattern matching, the first function expression with a matching pattern for the argument being passed in is the function expression that gets executed. So, if we have gotten this far, there has been no other pattern match so we know that our list argument has more then 2 values. So, now we simply use one cons function to match on a list that has a first element, x1, and a link to the rest of the list, xs. Now we can use our trusty last function to compare x1 to the last element of the list. If they are not equal, we return false as we don’t have a palindrome.
  • However, it they are equal, we need to evaluate the right hand side of the boolean &&. In this case, we literally create a new list made up of all elements of the list minus the first and last element. By calling init on xs, we are omitting the first element, x1, and getting all elements except the last. Then we recursively call isPalindrome on this newly created list so that we can continue checking further and further into the original list until we get to 2 values that are not equal or an empty or 1 element list (after all other outer elements have been “chopped off” so to speak).

Conclusion

I hope all this might encourage more developers to delve into the beautiful language of Haskell. I started dipping my toes in a while back and, pretty quickly, I took the plunge and absolutely love coding in Haskell. I feel at my most relaxed as a developer when I am writing Haskell code. Building up small composable Haskell functions with a type system that protects you but never gets in your way really does lead to a joyful coding experience for me. I will follow up this post with some more dipping of the toes in Haskell to show how we can use the type system to make our isPalindrome function handle lists of any type…well almost any type.

What’s PCF All About?

PCF stands for Pivotal Cloud Foundry. Cloud Foundry is an open source Platform as a Service technology. It was originally created at VMWare and open-sourced. Pivotal (which now once again is part of VMWare) has its own Cloud Foundry offering. It takes the original Cloud Foundry open source project and combines it with other tools and technologies into a more enhanced Platform as a Service offering. While it can run single stand-alone apps or monoliths, Cloud Foundry shines with Cloud Native Applications. These are applications which embrace the 12 Factor App principles in order to provide scalability, resilience and consistency. A software product which is created following these principles usually consists of a suite of microservices. These are developed, deployed and monitoried by a number of small cross-functional teams embracing DevOps and Continuous Delivery philosophies in order to very frequently provide value for end users and receive feedback. The feedback is used to improve, not only the end product itself, but also the team processes and methodologies that go into developing, deploying and monitoring the software that produces the product.

So what’s this 12 Factor App thing?

It’s a set of heuristics to guide product engineering teams towards the development of applications (or microservices) that best take advantage of cloud tools and technologies to accomplish resilience, scalability and consistency. A whistle stop tour would be:

  • Treat your logs as a stream of events.
  • One codebase for but multiple deploys of each microservice.
  • Build and run processes should be separated.
  • Any management or admin tasks should be run as once off jobs.
  • Don’t have any shared dependencies. Each microservice should explicitly declare and isolate its dependencies.
  • If a microservice uses a backing service such as a DB, treat this as a resource.
  • If you want to scale out a microservice, use the process model to scale concurrently and/or scale horizontally across containers/VMs.
  • For a microservice to be useful, it usually needs to be exposed to other services or the outside world – use port binding to do this.
  • Don’t use config files. Instead specify a microservice’s config in the environment that is given to the microservce.
  • Model each microservice as one or more stateless processes – so no sessions please.
  • Your microservices should start up quickly and should shutdown gracefully.
  • No suprises with the environment please – your dev, staging, prod etc. environments should be as consistent as possible.

Generally "best practices" aren’t really "best practices". They are heuristics that a lot of teams have found to work well for them. So it’s best not to be too dogmatic about these but they do serve as a good guide.

So where does PCF run my apps?

This can be anywhere really – on premise, AWS, GCP etc. Cloud foundry has an abstraction layer over infrastructure as a service (IAAS) providers. This is the main engine of cloud foundry and is called Bosh. It means that microservices deployed on cloud foundry can run on top of most IAAS providers or can also run on premise if you have the hardware resources available to run a cloud foundry installation at the scale you desire. Pivotal provide a service called Pivotal Web Services (PWS) at https://run.pivotal.io/. This is the quickest way to get started with PCF. In a previous blog post, I showed how to get up and running here with a Kotlin/Spring Boot microservice.

So is it similar to packaging and deploying apps with docker images and containers?

Yep this is quite similar but not exactly the same. While cloud foundry does support running docker containers, it also can package binaries and their dependencies into what is called a Droplet. This is kind of like a docker image. This Droplet can then be instantiated inside a cloud foundry container called a Garden container. A container is a logical unit for a process and it’s dependencies. It runs on top of a VM which is shared by other containers but it’s resources, memory, file system mounts and networking are completely isolated from other containers. Cloud foundry leverages Linux cgroups to do this which is a mechanism Linux uses to isolate things like memory and networking using namespaces. Because a container doesn’t have to include a complete operating system and can use the same underlying kernal resources as other containers on a VM, it is very fast to spin up. A container is built in layers. Things that all containers need like the base OS file system are contained in a read-only layer that each container can share. Things that are specific to a container are contained in its read-write layer which is not shared. Its worth noting too that cloud foundry also supports Windows containers. A container is deployed to a Diego Cell using the cell’s Garden API. This API is an implementation of the Open Container Initiative, thus allowing Docker containers to be deployed to Diego Cells.

How are these garden containers orchestrated?

This is taken care of by cloud foundry’s Diego system. This is a bit like Kubernetes. It is a self-healing container management system. Containers run inside what are called Diego Cells. A Diego cell is a VM – a bit like an EC2 instance in AWS (and could well be running on an EC2 instance if your cloud foundry installation/platform is using AWS as its IAAS provider). There is a component called the Diego Brain. This holds what are called auctions using its Auctioneer component. Diego cells participate in these auctions so that apps/microservices/tasks can be evenly distributed among Diego cells. Cloud foundry also contains what is called a Bulletin Board System (BBS). This keeps track of the actual number of instances of each microservice and the desired number of instances of each so that discrepancies can be rectified through initiating a Diego Brain auction process.

So how do I get my microservice onto PCF?

As shown in a previous blog post, cloud foundry can be used through a command line interface. So to deploy a JVM based application, it’s as simple as running cf push

That’s a bit too much magic for me. What actually happens when I do a cf push?

CLI commands such as this are sent as requests to the Cloud Controller component in Cloud Foundry (also called "CAPI"). The CAPI sends requests to Diego through the Bulletin Boards System. The CAPI component also contains a blobstore. This stores all sorts of things such as metadata about applications (name, desired instance count etc.), application pacakges, droplets, buildpacks and a buildpack cache (I’ll get to build packs in a sec). When the cf push command sends its request to the CAPI, metadata about the application being pushed is uploaded. This is contained in what is called a manifest.yml file. It contains details such as app name, number of instances, memory, disc quota etc. Following this, an "Uploading" stage is initiaited followed by a "Staging" stage which is followed by the "Running" stage.

In the uploading stage, first there is a check to see if any of the application’s packages already exist in the blobstore to save us from having to upload more then we need to. After this check, the CAPI initiates an upload of the required application packages from the developer’s machine. These are combined with existing application packages in the blobstore to form an overall application package.

After the uploading stage, the CAPI sends a request to Diego to schedule the "Staging" process. Diego schedules this to run on a Diego Cell. Before going further, it is helpful to know a bit about buildpacks.

A buildpack is something that is run to put together all the runtime support and dependencies such as framework dependencies required to run an app/microservice. The result of this process is a Droplet which is essentially an image that can be instantiated in a Diego container. The buildpack for a microservice can be specified it’s manifest.yml file. If it is not specified, cloud foundry will go through a process to determine which buildpack should be used. It does this by cycling through potential buildpacks which it downloads or gets from the CAPI buildpack cache. It does this by running each buildpack’s "Detect" script to determine if it is a suitable buildpack for the appication being built.

For example, calling “`cf push“ on a JVM jar file will eventually trigger this staging process for the uploaded jar. The staging process will find the buildpack capable of packaging a JVM based application. This buildpack will package up the required JVM runtime and other required dependencies like DB drivers etc.

After the Droplet is created from the buildpack, it is stored in the CAPI cache. The CAPI is notified that the Droplet is available so it then instructs Diego to run the application by instantiating Droplet as a container.

How do requests get into and responses come out of my microservice on PCF?

This happens through the Cloud Foundry component called the "Go Router". It routes requests coming in to applications running in containers on Diego cells or to Cloud Foundry components themselves. When a microservice is deployed to PCF, its route can be specified in its associated manifest.yml file or PCF can assign a random route. Also further routes can later be created and mapped or unmapped to or from applications. This enables blue-green deployments. This is where a temporary route can be mapped to a new version of a microservice. When we are happy that this new version is behaving and performing correctly, we can map the real route to this new version.

But can I restrict access to my services and restrict data coming from them?

Similar to AWS security groups and Network Access Control groups, application security groups can be set up to restrict the ports, ip address ranges and protocols that can be used to send data from a microservice to the outside world. Cloud Foundry also has a User Account and Authentication Service (UAA) which is an OAuth token issuer for microservices and other components to talk to each other. Also, PCF organizes user roles, resourecs, applicaiton deployments into scoped logical units call Orgs and Spaces.

What are Orgs and Spaces all about?

These are how Cloud foundry scopes things like user roles, application deployments, services and resources. There are two levels of scoping: the org level and the space level. You can choose how you want to model your orgs and spaces. For example, your orgs could correspond to different product offerings and each space inside an org could correspond to an environment such as dev, QA, prod etc. When logging into PCF using the CLI, you can specify which org and space combination you are targeting and can change at any point to target a different org-space combination. Users are created and scoped to orgs and spaces and have different roles. A whislestop tour of these roles would be:

  • Org Manager. This person can add users and their permissions at the org level; she/he can also add private domains and add or remove spaces among other capabilities.
  • Org Billing Manager and Org Auditors. These roles give read-only access to view things like application statuses and org quotas.
  • Space Manager. This person can add users and permissions at the space level among other capabilites.
  • Space Developer. This person can do things like deploy applications, create services, bind services to applications and bind routes to applications etc.

You mention services there, what are they?

Cloud foundry allows services to be created from service brokers. A service broker is a factory from which a service instance is created e.g a MySql db instance. There are different service brokers for all sorts of services e.g. MongoDB, MySql, Redis etc. These are available on the Cloud Foundry Marketplace. Each service broker specifies different plans – for example some have a free version with restrictions. It is also possible to create your own service broker. Service instances can easily be mapped to microservices on PCF. It’s as simple as running the cf bind-service command. A service instance is scoped by an org-space combination.

How can I see what’s going on with my microservices on PCF?

Each Diego cell has what is called a Cell Rep. This component streams output from sysout and syserr along with metrics to another Cloud Foundry component called the Metron Agent. This then passes the log event stream and metrics onto the Loggregator component. Logs and metrics can be retrieved from Loggregater through the CLI command cf logs but, more powerfully, Loggregator aggregates these and exposes them through its "Firehose". Third party applications can plug into the "Firehouse" using what are called "Nozzles". This is how very powerful monitoring tools such as Datadog can plug into the log/metric event stream coming from your microservices deployed on PCF.

Conclusion

I hope this has given an overview of what PCF is all about. There is a lot more to PCF. When used with a powerful ecosystem such as Spring Cloud in the JVM space or .Net Core in the .NET space, it can greatly simplify the delivery and monitoring of microservices that follow the 12 Factor App Heuristics.

Easy Boarding with Kotlin and PCF

In my last post, we built a simple microservice and got quick and easy lift off to the cloud with the combination of Kotlin, Spring Cloud and PCF. This is great but there’s no point taking off and not carrying any passengers. Thankfully, with PCF auto-reconfiguration, there’s no more queues in the departure lounge. So, to show this, I’m going to take the simple expenses-service that we built before and add capacity for it to cruise along carrying data.

Thanks to Spring Boot, adding an in-memory db using H2 is a breeze. Thanks to PCF, replacing this with a MySql db in the cloud is almost given to us for free!

All the source code for this is again just in one Kotlin file ExpensesServiceApplication.kt. For the purpose of this little demo service, this is fine. The complete ExpensesServiceApplication.kt file is included at the end of this post.

First of all we need a few changes to the pom.xml to add the spring data jpa and H2 dependencies.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

and the Kotlin JPA compiler plugin so the complete Kotlin plugin configuration looks like this:

<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    <plugin>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-plugin</artifactId>
        <configuration>
            <args>
                <arg>-Xjsr305=strict</arg>
            </args>
                <compilerPlugins>
                    <plugin>spring</plugin>
                    <plugin>jpa</plugin>
               </compilerPlugins>
        </configuration>
       <dependencies>
            <dependency>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-noarg</artifactId>
                <version>${kotlin.version}</version>
            </dependency>
            <dependency>
                 <groupId>org.jetbrains.kotlin</groupId>
                 <artifactId>kotlin-maven-allopen</artifactId>
                 <version>${kotlin.version}</version>
            </dependency>
       </dependencies>
    </plugin>
</plugins>

With the H2 dependency in place, Spring Boot will detect this on start up during its classpath auto configuration scan. It will create the correct beans required to interact with H2. It also provides a H2 console so we’ll be able to run queries locally with ease. To enable this and to specify the path to the H2 console, we need the following in application.properties:

spring.h2.console.enabled=true
spring.h2.console.path=/h2

Spring Data really simplifies interacting with the db. I still like to de-couple db Entity objects from the core domain model. Its good for security and it keeps the core of the microservice simple without JPA @Entity and other annotations leaking everywhere. For the purpose of this demo service, I have all the code in one source file, but the JPA specific code could easily be extracted to another Maven module and plugged in. To enable this, let’s define an interface for our interactions with the whatever storage mechanism we want to use.

interface ExpenseRepository {
fun save(expense: Expense)
fun retrieveAll() : List<Expense>
}

For now, our repository will just support saving one expense and retrieving all expenses to keep things simple.

Now we can encapsulate all JPA related persistence code into one class. Again, if this microservice were to get any more complicated, this encapsulation would be better done using a separate Maven module.

@Repository
class ExpenseJpaRepository(@PersistenceContext private val entityManager: EntityManager) : ExpenseRepository {

enum class ExpenseType {
Meals, Travel, Fuel
}

@Entity
private class ExpenseEntity(
@Enumerated(EnumType.STRING)
val expenseType: ExpenseType,
val employeeId: UUID,
val submissionDate: ZonedDateTime,
val amount: BigDecimal,
val origin: String? = null,
val destination: String? = null,
@Enumerated(EnumType.STRING)
val mode: Mode? = null,
val distanceInKM: Int? = null) {

@Id
@GeneratedValue
var id: Long? = null
}

@Transactional
override fun save(expense: Expense) {
entityManager.persist(expense.toExpenseEntity())
}

override fun retrieveAll(): List<Expense> {
val criteria = entityManager.criteriaBuilder.createQuery(ExpenseEntity::class.java)
return entityManager.createQuery(criteria.select(criteria.from(ExpenseEntity::class.java)))
.resultList
.map { it.toExpense() }
}

private fun Expense.toExpenseEntity() : ExpenseEntity =
when(this) {
is Meals ->
ExpenseEntity(ExpenseType.Meals,
employeeId,
submissionDate,
amount)
is Travel ->
ExpenseEntity(ExpenseType.Travel,
employeeId,
submissionDate,
amount,
origin,
destination,
mode)
is Fuel ->
ExpenseEntity(ExpenseType.Fuel,
employeeId,
submissionDate,
amount,
distanceInKM = distanceInKM)
}

private fun ExpenseEntity.toExpense() : Expense {
fun <T> checkNonNull(fieldName: String, t: T?): T = t ?: throw RuntimeException("Non null field $fieldName has been stored and retrieved as null")

return when (this.expenseType) {
ExpenseType.Meals -> Meals(employeeId, submissionDate, amount)
ExpenseType.Travel -> Travel(employeeId, submissionDate, amount, checkNonNull("origin", origin), checkNonNull("destination", destination), checkNonNull("mode", mode))
ExpenseType.Fuel -> Fuel(employeeId, submissionDate, amount, checkNonNull("distanceInKm", distanceInKM))
}
}
}

One thing to note above is that the ExpenseEntity is a type specific to this ExpenseJpaRepository and it is kept very simple as just a representation of a flat row in the expense db table. There is a little bit of converting to and from the core Expense domain model and the ExpenseEntity but, thanks to Kotlin extension functions, this is very quick and easy to implement. I didn’t override equals and hashcode on ExpenseEntity as this can have complications around choosing properties and having to consider that instances can exist inside and outside of the JPA persistence context. It is well encapsulated and used purely to save and retrieve raw rows in the db. It’s never used for any business logic.

I used EntityManager directly in the above code. Spring Data makes CRUD operations far easier by providing interfaces such as CrudRepository. However using that here would have meant having to expose the ExpenseEntity type outside of the ExpenseJpaRepository. This wouldn’t matter if this were all separated into a separate module as the encapsulation would be at the module level.

So, great we have a couple of CRUD operations on the db. We need to provide a Restful gateway into these. The ExpenseController is very similar to how it was in my previous blog post. Now it is going to have an ExpenseRepository property which Spring Boot will kindly inject for us because we have annotated the ExpenseJpaRepository with @Repository above.

@RestController
@RequestMapping("/expenses")
class ExpenseController(val expenseRepository: ExpenseRepository) {

There’s also now two endpoints for saving a expense and retrieving all expenses.

@GetMapping
fun getExpenses(): List<ExpenseDTO> {
return expenseRepository.retrieveAll().map { it.toDto() }
}

@PostMapping
fun postExpenses(@RequestBody expenseDTO: ExpenseDTO) {
expenseDTO.toExpense().let(expenseRepository::save)
}

There’s a bit of converting to and from the core domain model to DTOs. Also, because we are serialising and deserialising to and from children of a sealed class, we can use some handy Jackson annotations to take care of this for us. So our DTO ADT (algebraic data type) now looks like this:

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes(
value = [
JsonSubTypes.Type(value = ExpenseDTO.MealsDTO::class, name = "meals"),
JsonSubTypes.Type(value = ExpenseDTO.TravelDTO::class, name = "travel"),
JsonSubTypes.Type(value = ExpenseDTO.FuelDTO::class, name = "fuel")
]
)

sealed class ExpenseDTO {
data class MealsDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : ExpenseDTO()
data class TravelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : ExpenseDTO()
data class FuelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : ExpenseDTO()
}

With all this in place (full source code is at the end of this blog post), we can crank up the microservice by running the main function in ExpensesServiceApplication.

With the expenses-service running, navigate to the H2 Console using http://localhost:8080/h2 . We get a login screen to log into H2. Use user “sa” and leave password blank. For the JDBC url we need, jdbc:h2:mem:testdb

After logging, in we can see our EXPENSE_ENTITY table.

We can execute sql queries or, to simply do a select *, we can just click into the table name and click Run. Either way, we will see that there is nothing in that table yet.

To get an expense in there, we can hit our POST endpoint with whatever you’re favourite Http Client is. I used POSTMAN to submit the following POST requests.

localhost:8080/expenses

{
“type”: “travel”,
“employeeId”: “80d4d699-019a-44db-b1ce-b5ad569e5daf”,
“submissionDate”: “2020-02-01T21:05:18.762655Z”,
“amount”: 343,
“origin”: “London”,
“destination”: “Cork”,
“mode”: “Air”
}

localhost:8080/expenses

{
“type”: “fuel”,
“employeeId”: “80d4d699-019a-44db-b1ce-b5ad569e5daf”,
“submissionDate”: “2020-02-01T21:05:18.762655Z”,
“amount”: 187,
“distanceInKM”: 870
}

localhost:8080/expenses

{
“type”: “meals”,
“employeeId”: “80d4d699-019a-44db-b1ce-b5ad569e5daf”,
“submissionDate”: “2020-01-18T21:05:18.762655Z”,
“amount”: 63
}

After submitting those, request we can now, query our H2 db and see the following results:

We can also now perform a GET request in POSTMAN or any http client:

localhost:8080/expenses

And see the following response:

[
{
"type": "travel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18.762655Z",
"amount": 343.00,
"origin": "London",
"destination": "Cork",
"mode": "Air"
},
{
"type": "fuel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18.762655Z",
"amount": 187.00,
"distanceInKM": 870
},
{
"type": "meals",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-01-18T21:05:18.762655Z",
"amount": 63.00
}
]

So, getting a microservice running locally with a H2 db is great for rapid development but it’s useless if its not in production. Luckily PCF makes it easy to get a MySql db up and running and to have our code connect to it without any code changes and barely any configuration. Once we log into the PCF console like we did in my previous blog post, we can click into “Marketplace.

Then we need to search for “mysql” – typing “mys” will do. Then choose “ClearDB MySQL Database” as below.

Then choose the free option.


Click to select this plan.

Now we can give the mysql service a name and we need to bind it to our expenses-service. This can be automated by creating Manifest.yml in our project. However, lets simply add it through the PCF console for now.

Then we can click to create the service and we’ll see the following:

So our db is up and running! Its also possible to get the db connection details so you can connect with a db tool of your choice.

All that remains now is to add a tiny bit of config to our microservice and to build and push it to PCF. In application.properties we need the following:


spring.jpa.hibernate.ddl-auto=update

Then in the root of our project, we can build the jar by running mvn clean install

To push to PCF, navigate to the target directory and run:

cf push expenses-service -p expenses-service-0.0.1-SNAPSHOT.jar

We then get out put similar to the following:


Pushing app expenses-service to org demo-1000 / space development as tom@tompriordev.com...
Getting app info...
Updating app with these attributes...
  name:                expenses-service
  path:                /Users/priort/d/expenses-service/target
  command:             JAVA_OPTS="-agentpath:$PWD/.java-buildpack/open_jdk_jre/bin/jvmkill-1.16.0_RELEASE=printHeapHistogram=1 -Djava.io.tmpdir=$TMPDIR -XX:ActiveProcessorCount=$(nproc) -Djava.ext.dirs=$PWD/.java-buildpack/container_security_provider:$PWD/.java-buildpack/open_jdk_jre/lib/ext -Djava.security.properties=$PWD/.java-buildpack/java_security/java.security $JAVA_OPTS" &amp;&amp; CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-3.13.0_RELEASE -totMemory=$MEMORY_LIMIT -loadedClasses=13612 -poolType=metaspace -stackThreads=250 -vmOptions="$JAVA_OPTS") &amp;&amp; echo JVM Memory Configuration: $CALCULATED_MEMORY &amp;&amp; JAVA_OPTS="$JAVA_OPTS $CALCULATED_MEMORY" &amp;&amp; MALLOC_ARENA_MAX=2 SERVER_PORT=$PORT eval exec $PWD/.java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -cp $PWD/. org.springframework.boot.loader.JarLauncher
  disk quota:          1G
  health check type:   port
  instances:           1
  memory:              1G
  stack:               cflinuxfs3
  services:
    mysql-instance
  routes:
    expenses-service.cfapps.io

Updating app expenses-service...

Most interestingly, we’ll see something like below in the output:

   -----&gt; Downloading Client Certificate Mapper 1.11.0_RELEASE from https://java-buildpack.cloudfoundry.org/client-certificate-mapper/client-certificate-mapper-1.11.0-RELEASE.jar (found in cache)
   -----&gt; Downloading Container Security Provider 1.16.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-security-provider/container-security-provider-1.16.0-RELEASE.jar (found in cache)
   -----&gt; Downloading Maria Db JDBC 2.5.1 from https://java-buildpack.cloudfoundry.org/mariadb-jdbc/mariadb-jdbc-2.5.1.jar (found in cache)
   -----&gt; Downloading Spring Auto Reconfiguration 2.11.0_RELEASE from https://java-buildpack.cloudfoundry.org/auto-reconfiguration/auto-reconfiguration-2.11.0-RELEASE.jar 

Because we have a mysql service bound to our expenses-service, PCF knows that it needs a JDBC driver for MySql (Maria DB). It also knows that it needs to do what is called “Auto Reconfiguration”. This means that it automatically configures the expenses-service to be able to connect to the MySql db we created earlier. This is seriously powerful and handy especially for quick prototyping and showing demos to customers! Fear not though, its also possible to add a lot more configuration in terms of db connection pools etc.

After the expenses-service has been deployed on PCF, we will see something like this:

     state     since                  cpu    memory         disk           details
#0   running   2020-02-02T11:56:24Z   0.0%   162.7M of 1G   150.9M of 1G   

Now we can submit HTTP requests as before except this time we can hit the endpoints of our expenses-service up in the cloud! We can submit POST requests as below. The URL will vary depending on the url given for your own deployment. The URL is configured as a "route" in PCF and can be found by going back through the output from the cf push we just ran and looking for something like:

routes:
    expenses-service.cfapps.io

Now, lets submit a POST request to create a travel expense: POST expenses-service.cfapps.io/expenses

{ 
    "type": "travel",
    "employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
    "submissionDate": "2020-02-01T21:05:18.762655Z",
    "amount": 343,
    "origin": "London",
    "destination": "Shannon",
    "mode": "Air" 
}

And a GET request gives us back all the expenses that we have already stored. I saved one earlier so that is also on the response below. GET expenses-service.cfapps.io/expenses

[
{
"type": "travel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18Z",
"amount": 343.00,
"origin": "London",
"destination": "Cork",
"mode": "Air"
},
{
"type": "travel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18Z",
"amount": 343.00,
"origin": "London",
"destination": "Shannon",
"mode": "Air"
}
]

So, there we have it – the combination of Kotlin, Spring Cloud and PCF simplifying what it takes to build a microservice, bind a db to it and have the whole lot running in the cloud with minimal fuss!

Complete ExpensesServiceApplication.kt

package com.somecompany.financials.expensesservice

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
import org.springframework.web.bind.annotation.*
import java.lang.RuntimeException
import java.math.BigDecimal
import java.time.*
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import javax.persistence.*
import javax.transaction.Transactional

@SpringBootApplication
class ExpensesServiceApplication

fun main(args: Array<String>) {
runApplication<ExpensesServiceApplication>(*args)
}

sealed class Expense
data class Meals(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : Expense()
data class Travel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : Expense()
data class Fuel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : Expense()

enum class Mode() {
Air, Rail, Sea, Taxi, RentedCar
}


@RestController
@RequestMapping("/expenses")
class ExpenseController(val expenseRepository: ExpenseRepository) {

private val expenses = ConcurrentLinkedQueue(
setOf( Meals( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2020, 1, 15, 16, 30), ZoneOffset.UTC),
23.toBigDecimal()),
Travel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 12, 14, 13, 12), ZoneOffset.UTC),
12.toBigDecimal(),
"Cork",
"London",
Mode.Air),
Fuel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 11, 19, 16, 30), ZoneOffset.UTC),
45.toBigDecimal(),
45),
Travel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 12, 1, 9, 37), ZoneOffset.UTC),
12.toBigDecimal(),
"Limerick",
"Dublin",
Mode.Rail),
Fuel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 2, 1, 16, 30), ZoneOffset.UTC),
12.toBigDecimal(),
45),
Meals( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 9, 4, 21, 10), ZoneOffset.UTC),
14.toBigDecimal())))

@GetMapping
fun getExpenses(): List<ExpenseDTO> {
return expenseRepository.retrieveAll().map { it.toDto() }
}

@PostMapping
fun postExpenses(@RequestBody expenseDTO: ExpenseDTO) {
expenseDTO.toExpense().let(expenseRepository::save)
}

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes(
value = [
JsonSubTypes.Type(value = ExpenseDTO.MealsDTO::class, name = "meals"),
JsonSubTypes.Type(value = ExpenseDTO.TravelDTO::class, name = "travel"),
JsonSubTypes.Type(value = ExpenseDTO.FuelDTO::class, name = "fuel")
]
)

sealed class ExpenseDTO {
data class MealsDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : ExpenseDTO()
data class TravelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : ExpenseDTO()
data class FuelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : ExpenseDTO()
}

private fun Expense.toDto() =
when (this) {
is Meals -> ExpenseDTO.MealsDTO(employeeId, submissionDate, amount)
is Travel -> ExpenseDTO.TravelDTO(employeeId, submissionDate, amount, origin, destination, mode)
is Fuel -> ExpenseDTO.FuelDTO(employeeId, submissionDate, amount, distanceInKM)
}

private fun ExpenseDTO.toExpense() =
when (this) {
is ExpenseDTO.MealsDTO -> Meals(employeeId, submissionDate, amount)
is ExpenseDTO.FuelDTO -> Fuel(employeeId, submissionDate, amount, distanceInKM)
is ExpenseDTO.TravelDTO -> Travel(employeeId, submissionDate, amount, origin, destination, mode)
}
}

interface ExpenseRepository {
fun save(expense: Expense)
fun retrieveAll() : List<Expense>
}

@Repository
class ExpenseJpaRepository(@PersistenceContext private val entityManager: EntityManager) : ExpenseRepository {

enum class ExpenseType {
Meals, Travel, Fuel
}

@Entity
private class ExpenseEntity(
@Enumerated(EnumType.STRING)
val expenseType: ExpenseType,
val employeeId: UUID,
val submissionDate: ZonedDateTime,
val amount: BigDecimal,
val origin: String? = null,
val destination: String? = null,
@Enumerated(EnumType.STRING)
val mode: Mode? = null,
val distanceInKM: Int? = null) {

@Id
@GeneratedValue
var id: Long? = null
}

@Transactional
override fun save(expense: Expense) {
entityManager.persist(expense.toExpenseEntity())
}

override fun retrieveAll(): List<Expense> {
val criteria = entityManager.criteriaBuilder.createQuery(ExpenseEntity::class.java)
return entityManager.createQuery(criteria.select(criteria.from(ExpenseEntity::class.java)))
.resultList
.map { it.toExpense() }
}

private fun Expense.toExpenseEntity() : ExpenseEntity =
when(this) {
is Meals ->
ExpenseEntity(ExpenseType.Meals,
employeeId,
submissionDate,
amount)
is Travel ->
ExpenseEntity(ExpenseType.Travel,
employeeId,
submissionDate,
amount,
origin,
destination,
mode)
is Fuel ->
ExpenseEntity(ExpenseType.Fuel,
employeeId,
submissionDate,
amount,
distanceInKM = distanceInKM)
}

private fun ExpenseEntity.toExpense() : Expense {
fun <T> checkNonNull(fieldName: String, t: T?): T = t ?: throw RuntimeException("Non null field $fieldName has been stored and retrieved as null")

return when (this.expenseType) {
ExpenseType.Meals -> Meals(employeeId, submissionDate, amount)
ExpenseType.Travel -> Travel(employeeId, submissionDate, amount, checkNonNull("origin", origin), checkNonNull("destination", destination), checkNonNull("mode", mode))
ExpenseType.Fuel -> Fuel(employeeId, submissionDate, amount, checkNonNull("distanceInKm", distanceInKM))
}
}
}

Easy lift off with Kotlin and PCF

For me, Kotlin, Spring Boot/Cloud and Pivotal Cloud Foundry (PCF) share a key thing in common – they make it so easy to get ideas into working, robust software as quickly as possible. PCF, like Spring Boot, takes an opinionated approach that looks after a lot of the heavy lifting required to get a suite of microservices into production along with providing monitoring capabilities. It abstracts the underlying cloud infrastructures such as AWS that it can deploy services to. At the same time though, it is possible to go under the hood in order to perform more custom operations tasks and configurations.

For a quick peak at this, lets write a microservice to handle employee expenses.

Prerequistes (all free!)

Creating a Kotlin Spring Boot Service to manage employee expenses

To get going, we need to create a Spring Boot project with the correct dependencies to create a Restful microservice. Luckily this is straight forward using Spring Initializer at:

Select the options as in the screenshot below: Maven, Kotlin and Spring Web for the dependencies.

We’ll give the project a group id of com.somecompany.financials and an artifact id of expenses-service After the Spring Web dependency is chosen, you should see the following:

Click "Generate" and "expenses-service.zip" will be downloaded.

Extract this to a directory of your choice and open in Intellij and the project will be imported automatically as a maven project. The Spring Initializer will have bootstrapped in the necessary dependencies and the service can be started by navigating to the ExpensesServiceApplication.kt file and simply executing the main method. This will start up an embedded tomcat server on port 8080.

This doesn’t do too much at the moment so lets just add a very simple endpoint which will simply return all expenses. One of the beauties of Kotlin is the ability to have multiple top level classes in the same file as opposed to having to create separate class files for each public class in a package. This is especially handy for trying out ideas quickly. As an application grows in size though, it can be easier to separate out these classes into separate files for better organization. All of the code for the rest of this blog post will simply be added in the ExpensesServiceApplication.kt file. Also, for now, we will just store expenses in memory.

The Expense is modelled as an ADT (Algebraic Data Type). I wrote about the advantages of ADTs in a previous post. It’s essentially specifying the exact types of expenses that are possible in our domain model. Later when using Kotlin’s when expression to perform an action on an Expense, the Kotlin compiler is kind enough to let us know if we forget to take care of one or more types of Expenses.

sealed class Expense {}
data class Meals(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : Expense()
data class Travel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : Expense()
data class Fuel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : Expense()

enum class Mode() {
   Air, Rail, Sea, Taxi, RentedCar
}

As a side note, in the above code, certain properties are duplicated across the subtypes of Expense e.g. submissionDate.
I usually favour keeping the sealed class supertype as minimal as possible containing little or no properties. It means that the subtypes are kept as flexible as possible. I would see the type of Expense as a "type constructor" (with no type parameter in this case) and each subtype – e.g. Meals – as a "data term value".

In order to create an endpoint to return expenses, we can create a Spring Controller as follows: (again this is added directly in ExpensesServiceApplication.kt)

@RestController
@RequestMapping("/expenses")
class ExpenseController

In order to have expenses to return from our endpoint, lets just create an in-memory collection and lets keep it thread safe as we may want to add other endpoints later. A ConcurrentLinkedQueue will do the job for now. Lets add this to our Controller:

private val expenses = ConcurrentLinkedQueue<Expense>(
   setOf( Meals(  UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2020, 1, 15, 16, 30), ZoneOffset.UTC),
               23.toBigDecimal()),
         Travel(    UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 12, 14, 13, 12), ZoneOffset.UTC),
               12.toBigDecimal(),
               "Cork",
               "London",
               Mode.Air),
         Fuel(  UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 11, 19, 16, 30), ZoneOffset.UTC),
               45.toBigDecimal(),
               45),
         Travel(    UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 12, 1, 9, 37), ZoneOffset.UTC),
               12.toBigDecimal(),
               "Limerick",
               "Dublin",
               Mode.Rail),
         Fuel(  UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 2, 1, 16, 30), ZoneOffset.UTC),
               12.toBigDecimal(),
               45),
         Meals( UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 9, 4, 21, 10), ZoneOffset.UTC),
               14.toBigDecimal())))

This takes advantage of the handy setOf function in kotlin to create a Set of Expenses initialized with a few expenses so that we have some thing to return from our GET endpoint.

We can separate our core Expense domain model from the data that we will be returning from our endpoint with a DTO structure that essentially mirrors the Expense type shown earlier. Similar to languages like Scala and F#, this is quite concise in Kotlin and doesn’t involve having to create separate class files for each of the subtypes. Its shown in the code below as well as a conversion function to convert from a core Expense to a DTO expense. This function takes advantage of the extension method mechanism in Kotlin so that later, the function can be called on an Expense object as if it were a method on the Expense class.

sealed class ExpenseDTO(val type: ExpenseType) {
   data class MealsDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : ExpenseDTO(ExpenseType.Meals)
   data class TravelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : ExpenseDTO(ExpenseType.Travel)
   data class FuelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : ExpenseDTO(ExpenseType.Fuel)
}

enum class ExpenseType {
   Meals, Travel, Fuel
}

private fun Expense.toDto() =
   when (this) {
      is Meals -> ExpenseDTO.MealsDTO(employeeId, submissionDate, amount)
      is Travel -> ExpenseDTO.TravelDTO(employeeId, submissionDate, amount, origin, destination, mode)
      is Fuel -> ExpenseDTO.FuelDTO(employeeId, submissionDate, amount, distanceInKM)
   }

Now we are ready to create an endpoint to return our in-memory expenses as follows:

@GetMapping("")
fun getExpenses() = expenses.toSet().map { it.toDto() }

Now, we can restart the app by running the main method on ExpensesServiceApplication.kt. We can call our endpoint to get all expenses in the browser, using curl, Postman or, if you’re using Intellij Ultimate, a http scratch file will do the job.

We can see our expenses getting returned in the response:

[
  {
    "employeeId": "42f5af92-d8aa-4e35-a23d-b0010d7a9b47",
    "submissionDate": "2020-01-15T16:30:00Z",
    "amount": 23,
    "type": "Meals"
  },
  {
    "employeeId": "9633a8f0-7dd0-4916-bff3-8a71ef6d3063",
    "submissionDate": "2019-12-14T13:12:00Z",
    "amount": 12,
    "origin": "Cork",
    "destination": "London",
    "mode": "Air",
    "type": "Travel"
  },
  {
    "employeeId": "7bbb8954-1642-4b9e-a530-a186cca1da9e",
    "submissionDate": "2019-11-19T16:30:00Z",
    "amount": 45,
    "distanceInKM": 45,
    "type": "Fuel"
  },
  {
    "employeeId": "78253ec9-309d-47af-a24a-87f9585cd5eb",
    "submissionDate": "2019-12-01T09:37:00Z",
    "amount": 12,
    "origin": "Limerick",
    "destination": "Dublin",
    "mode": "Rail",
    "type": "Travel"
  },
  {
    "employeeId": "afe82285-24be-4eeb-bc1d-6c7a3356e1a2",
    "submissionDate": "2019-02-01T16:30:00Z",
    "amount": 12,
    "distanceInKM": 45,
    "type": "Fuel"
  },
  {
    "employeeId": "58c7d770-a6de-4e76-ab18-f0e703dcf8b4",
    "submissionDate": "2019-09-04T21:10:00Z",
    "amount": 14,
    "type": "Meals"
  }
]

That’s it for the microservice logic. The great thing is, with Kotlin, this can all be simply coded in the ExpensesServiceApplication.kt file. For a tiny microservice this can be fine as it keeps things simple. If it gets more complex, Intellij refactoring tools make it a breeze to pull out parts of this code into other files. For completeness, the entire ExpensesServiceApplication.kt is below:

package com.somecompany.financials.expensesservice

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.math.BigDecimal
import java.time.*
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue

@SpringBootApplication
class ExpensesServiceApplication

fun main(args: Array<String>) {
   runApplication<ExpensesServiceApplication>(*args)
}

sealed class Expense {}
data class Meals(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : Expense()
data class Travel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : Expense()
data class Fuel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : Expense()

enum class Mode() {
   Air, Rail, Sea, Taxi, RentedCar
}


@RestController
@RequestMapping("/expenses")
class ExpenseController {

private val expenses = ConcurrentLinkedQueue<Expense>(
   setOf( Meals(  UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2020, 1, 15, 16, 30), ZoneOffset.UTC),
               23.toBigDecimal()),
         Travel(    UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 12, 14, 13, 12), ZoneOffset.UTC),
               12.toBigDecimal(),
               "Cork",
               "London",
               Mode.Air),
         Fuel(  UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 11, 19, 16, 30), ZoneOffset.UTC),
               45.toBigDecimal(),
               45),
         Travel(    UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 12, 1, 9, 37), ZoneOffset.UTC),
               12.toBigDecimal(),
               "Limerick",
               "Dublin",
               Mode.Rail),
         Fuel(  UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 2, 1, 16, 30), ZoneOffset.UTC),
               12.toBigDecimal(),
               45),
         Meals( UUID.randomUUID(),
               ZonedDateTime.of(LocalDateTime.of(2019, 9, 4, 21, 10), ZoneOffset.UTC),
               14.toBigDecimal())))


   @GetMapping("")
   fun getExpenses() = expenses.toSet().map { it.toDto() }

   sealed class ExpenseDTO(val type: ExpenseType) {
      data class MealsDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : ExpenseDTO(ExpenseType.Meals)
      data class TravelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : ExpenseDTO(ExpenseType.Travel)
      data class FuelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : ExpenseDTO(ExpenseType.Fuel)
   }

   enum class ExpenseType {
      Meals, Travel, Fuel
   }

   private fun Expense.toDto() =
      when (this) {
         is Meals -> ExpenseDTO.MealsDTO(employeeId, submissionDate, amount)
         is Travel -> ExpenseDTO.TravelDTO(employeeId, submissionDate, amount, origin, destination, mode)
         is Fuel -> ExpenseDTO.FuelDTO(employeeId, submissionDate, amount, distanceInKM)
      }
}

Preparing for lift off

To get our expenses microservice into the cloud on PCF, we need to first create a PCF account. So first we need to go to PCF and set up an account. Click the sign up for free button underneath which at the time of writing this blog it specifies that $87 of free trial credit is given.

This takes us to a sign up page where we can enter details to create an account.

After filling in details and clicking "Sign Up", we are brought to a screen that lets us know that an account verification email has been sent to us. Once we check our email and verify, we can now sign into our PCF account by going to the PCF Login

After signing in, we are brought to a main screen

Click into "Pivotal Web Services" This brings us to a wizard to add a few more details to claim our free trial.

As part of going through this wizard, we can create an "Org" which is essentially an account in pcf for developers to collaborate and deploy services and computing resources etc. The org name can be changed later so for now, we can just call it demo-1000. This name needs to be unique, so if you are following along, "demo-" with some high number will do the trick.

This brings us a portal to manage our org.

We can see that there is one "space" within the Org called "development" by default. A space is an environment like "development", "qa" "production" etc. This is where we will be deploying our expenses-service to.

So far we have interacted with PCF through the online portal. However, going forward we will want to automate everything. PCF provides a command line interface called "cf" which can be downloaded from here:.

Lifting Off

With the cf command line interface installed, we can log into our pcf account with

cf login -a https://api.run.pivotal.io

After logging in, it will output something like below:

Authenticating...
OK

Targeted org demo-1000

Targeted space development


                
API endpoint:   https://api.run.pivotal.io (API version: 2.144.0)

Before we can push the expenses-service to the cloud we need to run mvn clean install in the root directory of the project to produce an executable jar containing our microservice. This produces expenses-service-0.0.1-SNAPSHOT.jar in the target directory.

We can now lift this off to the cloud by simply navigating to the target directory and running.

cf push

PCF uses what are called buildpacks to package and deploy apps. It is clever enough to know that we are deploying a JVM based microservice so in the output of the cf push command, we can see that it is using the java buildpack.

Creating app expenses-service...
Mapping routes...


   Downloaded app package (18.8M)
   -----&gt; Java Buildpack v4.26 (offline) | https://github.com/cloudfoundry/java-buildpack.git#e06e00b
   -----&gt; Downloading Jvmkill Agent 1.16.0_RELEASE from https://java-buildpack.cloudfoundry.org/jvmkill/bionic/x86_64/jvmkill-1.16.0-RELEASE.so (found in cache)
   -----&gt; Downloading Open Jdk JRE 1.8.0_232 from https://java-buildpack.cloudfoundry.org/openjdk/bionic/x86_64/openjdk-jre-1.8.0_232-bionic.tar.gz (found in cache)

Waiting for app to start...

name:              expenses-service
requested state:   started
routes:            expenses-service.cfapps.io
last uploaded:     Sun 12 Jan 11:01:21 GMT 2020
stack:             cflinuxfs3
buildpacks:        client-certificate-mapper=1.11.0_RELEASE container-security-provider=1.16.0_RELEASE
                   java-buildpack=v4.26-offline-https://github.com/cloudfoundry/java-buildpack.git#e06e00b java-main java-opts
                   java-security jvmkill-agent=1.16.0_RELEASE open-jdk...

type:            web
instances:       1/1
memory usage:    1024M

When it has finished deploying the app, we will see something like the following in the output:

     state     since                  cpu    memory         disk         details
#0   running   2020-01-12T11:01:54Z   0.0%   128.6M of 1G   130M of 1G

We can run the command

cf apps

to see some info about our running microservice. This will output:

name               requested state   instances   memory   disk   urls
expenses-service   started           1/1         1G       1G     expenses-service.cfapps.io

The url will vary on your machine and may contain a random postfix after "expenses-service". Now we can navigate to

http://expenses-service.cfapps.io/expenses

(or whatever url is displayed in the "urls" column above if you are following along)in the browser, or any http client, and see our expenses being returned!

GET http://expenses-service.cfapps.io/expenses

HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 12 Jan 2020 11:12:47 GMT
X-Vcap-Request-Id: 7b9d0de2-e1fa-4599-4a55-a290b1718f6a
Content-Length: 872
Connection: keep-alive

[
  {
    "employeeId": "8c1416ed-54f5-4003-b422-d4b1e5fc92ae",
    "submissionDate": "2020-01-15T16:30:00Z",
    "amount": 23,
    "type": "Meals"
  },
  {
    "employeeId": "e0438bed-d35f-4d5a-a00c-900b29ba6a90",
    "submissionDate": "2019-12-14T13:12:00Z",
    "amount": 12,
    "origin": "Cork",
    "destination": "London",
    "mode": "Air",
    "type": "Travel"
  },
  {
    "employeeId": "a01235c8-e919-4a26-ad2b-617928b3aed4",
    "submissionDate": "2019-11-19T16:30:00Z",
    "amount": 45,
    "distanceInKM": 45,
    "type": "Fuel"
  },
  {
    "employeeId": "6021b0ce-6c3c-41ad-92dc-c00cc00bdc0f",
    "submissionDate": "2019-12-01T09:37:00Z",
    "amount": 12,
    "origin": "Limerick",
    "destination": "Dublin",
    "mode": "Rail",
    "type": "Travel"
  },
  {
    "employeeId": "b71e7f48-c8e6-4718-abef-338740548ef2",
    "submissionDate": "2019-02-01T16:30:00Z",
    "amount": 12,
    "distanceInKM": 45,
    "type": "Fuel"
  },
  {
    "employeeId": "6c24924c-3dd9-4c12-957d-eead504dfb32",
    "submissionDate": "2019-09-04T21:10:00Z",
    "amount": 14,
    "type": "Meals"
  }
]

In this post, hopefully I have shown the power of the Kotlin, Spring Boot and PCF combination. This is even more convenient and powerful when it comes to combining it with Spring Cloud and building, deploying and monitoring a whole suite of microservices. This blog post also allowed PCF to take its most opinionated approach to deploying our jar to the cloud. However, a lot more control and configuration is possible through using a manifest.yml file to better configure and automate deployment along with other mechanisms to gain more lower level control.