30Mar

Writing a microservice in Go Swagger, Part 1: The Specification.

Posted by Elf Sternberg as Uncategorized

Introduction

${WORK} has me writing microservices in Go, using OpenAPI 2.0 / Swagger. While I’m not a fan of Go (that’s a bit of an understatement) I get why Go is popular with enterprise managers, it does exactly what it says it does. It’s syntactically hideous. I’m perfectly happy taking a paycheck to write in it, and I’m pretty good at it already. I just wouldn’t choose it for a personal project.

But if you’re writing microservices for enterprise customers, yes, you should use Go, and yes, you should use OpenAPI and Swagger. So here’s how it’s done.

All of the files for this tutorial are available from the elfsternberg/go-swagger-tutorial repo at github. There are two phases to this tutorial, and the first phase is the base Go Swagger implementation. I strongly recommend that if you’re going to check out the source code in its entirety, that you start with the Basic Version, and only check out the Advanced version when you get to Part 3.

Just be aware that if you see stuff that looks like <<this>>, or a single @ alone on a line, that’s just part of my code layout; do not include those in your source code, they’re not part of Go or Swagger. Sorry about that.

Go Swagger!

Swagger is a specification that describes the ndpoints for a webserver’s API, usually a REST-based API. HTTP uses verbs (GET, PUT, POST, DELETE) and endpoints (/like/this) to describe things your service handles and the operations that can be performed against it.

Swagger starts with a file, written in JSON or YAML, that names each and every endpoint, the verbs that endpoint responds to, the parameters that endpoint requires and takes optionally, and the possible responses, with type information for every field in the inputs and outputs.

Swagger tooling then takes that file and generates a server ready to handle all those transactions. The parameters specified in the specification file are turned into function calls and populated with "Not implemented" as the only thing they return.

Your job

In short, for a basic microservice, it’s your job to replace those functions with your business logic.

There are three things that are your responsibility:

  1. Write the specification that describes exactly what the server accepts as requests and returns as responses, and generate a server from this specification.
  2. Write the business logic.
  3. Glue the business logic into the generated server.

In Go-Swagger, there is exactly one file in the generated code that you need to change. Every other file is labeled "DO NOT EDIT." This one file, called configure_project.go, has a top line that says "This file is safe to edit, and will not be replaced if you re-run swagger." That exactly one file should be the only one you ever need to change.

The setup

You’ll need Go. I’m not going to go into setting up Go on your system; there are perfectly adequate guides elsewhere. You will need to install swagger and dep.

Once you’ve set up your Go environment (set up $GOPATH and $PATH), you can just:

$ go get -u github.com/golang/dep/cmd/dep
$ go get -u github.com/go-swagger/go-swagger/cmd/swagger

Initialization

Now you’re going to create a new project. Do it in your src directory somewhere, under your $GOPATH.

$ mkdir project timeofday
$ cd timeofday
$ git init && git commit --allow-empty -m "Init commit: Swagger Time of Day."
$ swagger init spec --format=yaml --title="Timeofday" --description="A silly time-of-day microservice"

You will now find a new swagger file in your project directory. If you open it up, you’ll see short header describing the basic features Swagger needs to understand your project.

Operations

Swagger works with operations, which is a combination of a verb and an endpoint. We’re going to have two operations which do the same thing: return the time of day. The two operations use the same endpoint, but different verbs: GET and POST. The GET argument takes an optional timezone as a search option; the POST argument takes an optional timezone as a JSON argument in the body of the POST.

First, let’s version our API. You do that with Basepaths:

<<version the API>>=
basePath: /timeofday/v1
@

Now that we have a base path that versions our API, we want to define our endpoint. The URL will ultimately be /timeofday/v1/time, and we want to handle both GET and POST requests, and our responses are going to be Success: Time of day or Timezone Not Found.

<<define the paths>>=
paths:
  /time:
    get:
      operationId: "GetTime"
      parameters:
        - in: path
          name: Timezone
          schema:
            type: string
            minLength: 3
      responses:
        200:
          schema:
            $ref: "#/definitions/TimeOfDay"
        404:
          schema:
            $ref: "#/definitions/NotFound"
    post:
      operationId: "PostTime"
      parameters:
        - in: body
          name: Timezone
          schema:
            $ref: "#/definitions/Timezone"
      responses:
        200:
          schema:
            $ref: "#/definitions/TimeOfDay"
        404:
          schema:
            $ref: "#/definitions/NotFound"
@

The $ref entries are a YAML thing for referring to something else. The octothorpe symbol (#) indicates "look in the current file." So now we have to create those paths:

<<schemas>>=
definitions:
  NotFound:
    type: object
    properties:
      code:
        type: integer
      message:
        type: string
  Timezone:
    type: object
    properties:
      Timezone:
        type: string
        minLength: 3
  TimeOfDay:
    type: object
    properties:
      TimeOfDay: string
@

This is really verbose, but on the other hand it is undeniably complete: these are the things we take in, and the things we respond with.

So now your file looks like this:

<<swagger.yml>>=
swagger: "2.0"
info:
  version: 0.1.0
  title: timeofday
produces:
  - application/json
consumes:
  - application/json
schemes:
  - http

# Everything above this line was generated by the swagger command.
# Everything below this line you have to add:

<<version the API>>

<<schemas>>

<<define the paths>>
@

Now that you have that, it’s time to generate the server!

$ swagger generate server -f swagger.yml

It will spill out the actions it takes as it generates your new REST server. Do not follow the advice at the end of the output. There’s a better way. Use dep, which will automagically find all your dependencies for you, download them to a project-specific vendor/ folder, and lock the specific commit in the record so version creep won’t break your project in the future. dep has become even Google’s recommended dependency control mechanism. Just run:

$ dep init

Dependency management in Go is a bit of a mess, but the accepted solution now is to use dep rather than go get. This creates a pair of files, one describing the Go packages that your file uses, and one describing the exact versions of those packages that you last downloaded and used in the ./vendor/ directory under your project root.

Now you can build the server:

$ go build ./cmd/timeofday-server/

And then you can run it. Feel free to change the port number:

$ ./timeofday-server --port=8082

You can now tickle the server:

$ curl http://localhost:8082/
{"code":404,"message":"path / was not found"}
$ curl http://localhost:8082/timeofday/v1/time
" function .GetTime is not implemented"

Congratulations! You have a working REST server that does, well, nothing.

For Part 2, we’ll make our server actually do things.

Comment Form

Subscribe to Feed

Categories

Calendar

March 2018
M T W T F S S
« Feb   Apr »
 1234
567891011
12131415161718
19202122232425
262728293031