How I create a Microservice using goa

#goadesigntokyo

2016/09/06


Who am I

@tchssk (Taichi Sasaki)

Engineer at Ebisol, Inc.


Agenda


Why I use goa


Swagger (OpenAPI)

The World’s Most Popular Framework for APIs.

http://swagger.io/


Some generators for Swagger

I decided to use goa.


How I use goa

  1. Write design
  2. Generate codes by goagen
  3. Implement controllers

Let me repeat it.


Directories

.
├── app
├── client
├── controllers  # 3. Implement controllers
├── db
├── design       # 1. Write design
├── models
├── swagger
└── tool

I put controllers into controllers directory.


design Directory

├── design
│   ├── api_definition.go  # apidsl.API()
│   ├── media_types.go     # apidsl.MediaType()
│   ├── models.go          # dsl.StorageGroup()
│   ├── resources.go       # apidsl.Resource()
│   ├── security.go        # apidsl.JWTSecurity()
│   └── user_types.go      # apidsl.Type()

design is separated by each top level DSL.


What I use with goa


Gorma

Storage generation plugin for Goa.

https://github.com/goadesign/gorma


What is Gorma


How to use Gorma

  1. Write design
  2. Generate codes by goagen
  3. Use generated codes in controllers

An example – Geolocation API


1. Write design
var sg = StorageGroup("MyStorageGroup", func() {
	Store("mysql", gorma.MySQL, func() {
		Model("Location", func() {
			RendersTo(Location)  // This will generate a helper.
			Field("name", gorma.String)
			Field("latitude", gorma.Decimal)
			Field("longitude", gorma.Decimal)
		})
	})
})

2. Generate codes by goagen
$ goagen gen \
--design=github.com/tchssk/go-study/design \
--pkg-path=github.com/goadesign/gorma

goagen generates following code for Gorm

// Location Relational Model
type Location struct {
	ID        int `gorm:"primary_key"`  // This is added by default.
	CreatedAt time.Time                 // This is added by default.
	DeletedAt *time.Time                // This is added by default.
	Latitude  float32
	Longitude float32
	Name      string
	UpdatedAt time.Time                 // This is added by default.
}

and some helpers.


3. Use generated codes in controllers
func (c *LocationController) Show(
	ctx *app.ShowLocationContext
) error {
	ldb := models.NewLocationDB(db)  // db is the instance of Gorm.
	location, err := ldb.OneLocation(ctx)       // Helper.
	if err != nil {
		return ctx.NotFound()
	}
	return ctx.OK(location.LocationToLocation)  // Helper.
}

These are useful helper methods generated by Gorma.

models.NewLocationDB()
// It creates a new storage type.

ldb.OneLocation()
// It gets one location from database.

location.LocationToLocation()
// It converts location to response media type.
// This is generated from RendersTo().

Docker

The open-source application container engine.

https://github.com/docker/docker

Maybe you are more familiar than me.


What I use docker for

Creating a contaier of MySQL for


Local Development Environment
  1. Start a MySQL container.
  2. Start a server with connection to the container.

Testing

Dockertest

Dockertest library provides easy to use commands for spinning up Docker containers and using them for your tests.

https://github.com/ory-am/dockertest


How I use Dockertest

In TestMain(),

  1. Start a container by Dockertest.
  2. Create fixtures (test data) in the container.
  3. Execute tests.
  4. Close DB connection and clean up the container.

TestMain()
func TestMain(m *testing.M) {
	c, err := dockertest.ConnectToMySQL(15, time.Second,
		func(url string) bool {
			SetDB(database.GetWithURL(url)) // Open DB connection.
			return true
		},
	)
	if err != nil {
		log.Fatalf("Could not connect to database: %s", err)
	}                 // 1. Start a continer.
	CreateFixtures()  // 2. Create fixtures.
	result := m.Run() // 3. Execute tests.
	db.Close()        // 4. Close DB connection.
	c.KillRemove()    // 4. Clean up the container.
	os.Exit(result)
}

go test
$ export DOCKERTEST_MYSQL_IMAGE_NAME=mysql:5.6
$ go test ./...

Dockertest provides some envioronment variables.

https://github.com/ory-am/dockertest#i-need-to-use-a-specific-container-version-for-xyz


CI

Project is built and tested on CircleCI.

Below is circle.yml.

machine:
  environment:
    DOCKERTEST_BIND_LOCALHOST: true
    DOCKERTEST_MYSQL_IMAGE_NAME: mysql:5.6
  services:
    - docker

Swagger UI

Swagger UI is a dependency-free collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.

https://github.com/swagger-api/swagger-ui


How I use Swagger UI

  1. Clone files in dist directory of Swagger UI into swagger directory of my repository.
  2. Fix index.html to use ./swagger.json by default.
  3. Update design to serve the files.

     var _ = apidsl.Resource("swagger", func() {
     	apidsl.Origin("*", func() {
     		apidsl.Methods("GET")
     	})
     	apidsl.Files("/swagger.json", "swagger/swagger.json")
     	apidsl.Files("/*filepath", "swagger")
     })
    
  4. Regenerate codes.

ReDoc

OpenAPI/Swagger-generated API Reference Documentation.

https://github.com/Rebilly/ReDoc


API Console of ReDoc

API Console feature · Issue #53 · Rebilly/ReDoc

Coming soon?


When I contribute to Goa

I find some typo or bugs

Then I dive into goa and explore that.


Inside of goa


Contribution is welcomed


Would you like to use goa?

and contribute to that?