Just Give Me the Code

Fine...here you go.

Motivation

I've found myself writing a lot of RESTful microservices in go recently. Because of this, I developed the habit of copying and pasting a majority of code from one microservice to another and changing only 2 things: the data model and the resource nouns. Everything else normally stayed the same!

If you think about all of the aspects that a robust production application contains, you might compose a list like this:

  • Unit testing
  • Linting
  • Containerization
  • API documentation
  • Logging
  • Command line interface
  • Authorization
  • SSL support
  • CORS
  • Error handling
  • Database driver
  • Health probe

For a simple microservice, the difference between these items from app to app is, as I stated earlier, the data model and the resource nouns. Now, there might be one other difference, the database driver. One microservice might utilize mysql while another mongo. However, the majority of apps normally choose from a common few choices.

Let me give a concrete example of what I mean. Consider the following code:

func RetrieveCalls(db *database.Database, w http.ResponseWriter, r *http.Request) error {  
    result, err := db.RetrieveAll()
    if err != nil {
        return utils.StatusError{http.StatusInternalServerError, err}
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    if err := json.NewEncoder(w).Encode(result); err != nil {
        return utils.StatusError{http.StatusInternalServerError, err}
    }
    return nil
}

This code will retrieve all Calls from our database and write them to our http response. Now if my RESTful resource was People instead of Calls the only thing that changes is the method signature. This concept holds true throughout the entire codebase...minus the struct that holds our data model.

Because of this analysis, I thought this would be a great use case for a Yeoman template. Hence, it is one of my current active projects. Let's walk through it!

Requirements

Before getting started, let's make sure we have our environment setup correctly. Here's what we'll need:

  • Go: well of course...
  • Glide: a popular dependency management tool for go that makes it easy to version dependencies.
  • Docker: we'll use this to enhance our local development experience.
  • Yeoman: our template generator tool

Demo

$ npm install -g yo
$ npm install -g generator-gomicro
$ yo gomicro
=====================================================================
                         Welcome to gomicro!
This Yeoman generator aims to scaffold a robust RESTful microservice.

             [ERROR] GOPATH not properly configured

              Please fix the above errors and try again
=====================================================================
$ 

Go is very particular about how it wants its workspaces setup. To continue, we need to configure our GOPATH. Let's try again.

$ export GOPATH=~/Documents/projects/gopath
$ yo gomicro
=====================================================================
                         Welcome to gomicro!
This Yeoman generator aims to scaffold a robust RESTful microservice.  
                         Let's get started!
=====================================================================

=====================================================================
           Go workspaces like to be created a certain way
          (e.g. $GOPATH/src/github.com/frankgreco/gomicro)
      To do this, let's get some information about your project
=====================================================================

? your name Frank B Greco Jr
? your email frank@petrasphere.io
? vcs github.com
? github.com username frankgreco
? project name myapp
? resource noun (singular) phone
? resource noun (plural) phones
? database type mysql

=====================================================================
                   Creating your workspace...
=====================================================================

   create Documents/projects/gopath/src/github.com/frankgreco/myapp/Makefile
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/table.sql
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/Dockerfile
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/glide.yaml
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/swagger.json
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/docker-compose.yaml
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/.gitignore
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/main.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/route/logger.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/route/router.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/route/routes.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/cmd/root.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/cmd/start.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/cmd/version.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/utils/error.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/utils/flag.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/server/server.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/handler/handler.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/handler/util.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/database/driver.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/handler/phone.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/handler/phones.go
   create Documents/projects/gopath/src/github.com/frankgreco/myapp/models/phone.go

=====================================================================
      You're almost done! Your workspace has been created here:
               $GOPATH/src/github.com/frankgreco/myapp/
To complete your setup, run the following commands in your workspace:  
               $ make             (use your own database)
               $ make local-dev   (or, create a local database)
               $ ./myapp --help   (example usage)
=====================================================================
$

With the responses to just few user prompts, we are able to dynamically generate a robust code base that is a lot more than boilerplate!

It's time to actually start our newly generated application. To do this we'll use the make tool to install all of our dependencies, build our binary, and start a docker image using the backend that corresponds to the database driver we chose earlier.

$ cd $GOPATH/src/github.com/frankgreco/myapp/
$ make local-dev
glide update  
[INFO]    Downloading dependencies. Please wait...
[INFO]    --> Fetching updates for github.com/spf13/cobra.
[INFO]    --> Fetching updates for github.com/Sirupsen/logrus.
[INFO]    --> Fetching updates for github.com/gorilla/mux.
...
...
go build  
docker-compose up -d  
Creating network "myapp_default" with the default driver  
Creating mysql  
$ ./myapp --help                                                         
myapp is a RESTful microservice that performs CRUD operations on the Phone resources

Usage:  
  myapp [command]

Available Commands:  
  help        Help about any command
  start       start a new server
  version     print the version information

Use "myapp [command] --help" for more information about a command.  
$

Now that everything is installed and our binary works, let's start our app using the cli tools. If you don't want to use the cli tools, you can use environment variables too!

$ ./myapp start \               
 --app-port=8080 \
 --db-port=3306 \
 --db-host=localhost \
 --db-name=phones \
 --db-user=root \
 --db-pass=password

Finally, let's test our application. Since this is a RESTful CRUD microservice, we can preform RUD operations on the singular resource noun and CRUD operations on the plural resource noun.

$ curl -X POST -H "Content-Type: application/json" -d '{                                                                            
    "paramOne": "foo",
    "paramTwo": "bar"
}' "http://localhost:8080/phones"
{"id":1,"paramOne":"foo","paramTwo":"bar"}
$

Want to see something else cool! the /docs route will return dynamically generated Swagger documentation for your API!

At this point, the only thing that you have left to develop is to swap out the example data model with your own.

What's Next

This project is currently under active development and should be done shortly. Let me know what you think and if this would provide value to your development. Want to track this project's backlog? Find it here.