Documentation done, and Readme annotated with the appropriate links.

This commit is contained in:
Elf M. Sternberg 2018-03-30 22:26:12 -07:00
parent 406fee5458
commit 0237bc1d8a
3 changed files with 120 additions and 44 deletions

View File

@ -11,6 +11,23 @@ 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 should use Go, and yes, you should use OpenAPI and Swagger. So here's
how it's done. 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](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0),
and only check out the
[Advanced version](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.4.0)
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](https://swagger.io/) is a specification that describes the [Swagger](https://swagger.io/) is a specification that describes the
ndpoints for a webserver's API, usually a REST-based API. HTTP uses ndpoints for a webserver's API, usually a REST-based API. HTTP uses
verbs (GET, PUT, POST, DELETE) and endpoints (/like/this) to describe verbs (GET, PUT, POST, DELETE) and endpoints (/like/this) to describe
@ -36,12 +53,12 @@ functions with your business logic.
There are three things that are your responsibility: There are three things that are your responsibility:
1. Write the specification that describes *exactly* what the server 1. Write the specification that describes *exactly* what the server
accepts as requests and returns as responses. accepts as requests and returns as responses, and generate a server from
this specification.
2. Write the business logic. 2. Write the business logic.
3. Glue the business logic into the server generated from the 3. Glue the business logic into the generated server.
specification.
In Go-Swagger, there is *exactly one* file in the generated code that 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 you need to change. Every other file is labeled "DO NOT EDIT." This
@ -64,6 +81,7 @@ $ go get -u github.com/golang/dep/cmd/dep
$ go get -u github.com/go-swagger/go-swagger/cmd/swagger $ go get -u github.com/go-swagger/go-swagger/cmd/swagger
``` ```
## Initialization ## Initialization
Now you're going to create a new project. Do it in your src directory Now you're going to create a new project. Do it in your src directory
@ -91,15 +109,18 @@ optional timezone as a JSON argument in the body of the POST.
First, let's version our API. You do that with Basepaths: First, let's version our API. You do that with Basepaths:
```
<<version the API>>= <<version the API>>=
basePath: /timeofday/v1 basePath: /timeofday/v1
@ @
```
Now that we have a base path that versions our API, we want to define 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 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 want to handle both GET and POST requests, and our responses are going
to be **Success: Time of day** or **Timezone Not Found**. to be **Success: Time of day** or **Timezone Not Found**.
```
<<define the paths>>= <<define the paths>>=
paths: paths:
/time: /time:
@ -133,11 +154,13 @@ paths:
schema: schema:
$ref: "#/definitions/NotFound" $ref: "#/definitions/NotFound"
@ @
```
The `$ref` entries are a YAML thing for referring to something else. The `$ref` entries are a YAML thing for referring to something else.
The octothorpe symbol `(#)` indicates "look in the current file. So The octothorpe symbol `(#)` indicates "look in the current file." So
now we have to create those paths: now we have to create those paths:
```
<<schemas>>= <<schemas>>=
definitions: definitions:
NotFound: NotFound:
@ -158,6 +181,7 @@ definitions:
properties: properties:
TimeOfDay: string TimeOfDay: string
@ @
```
This is *really verbose*, but on the other hand it is *undeniably 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 complete*: these are the things we take in, and the things we respond
@ -165,6 +189,7 @@ with.
So now your file looks like this: So now your file looks like this:
```
<<swagger.yml>>= <<swagger.yml>>=
swagger: "2.0" swagger: "2.0"
info: info:
@ -186,14 +211,19 @@ schemes:
<<define the paths>> <<define the paths>>
@ @
```
Now that you have that, it's time to generate the server! Now that you have that, it's time to generate the server!
`$ swagger generate server -f swagger.yml` `$ swagger generate server -f swagger.yml`
It will spill out the actions it takes as it generates your new REST 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. server. **Do not** follow the advice at the end of the output. There's
There's a better way. 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` `$ dep init`
@ -224,4 +254,4 @@ $ curl http://localhost:8082/timeofday/v1/time
Congratulations! You have a working REST server that does, well, Congratulations! You have a working REST server that does, well,
nothing. nothing.
For part two, we'll make our server actually do things. For Part 2, we'll make our server actually do things.

View File

@ -1,36 +1,45 @@
# Review of Part One # Review of Part One
In [Part One of Go-Swagger](TK:), we generated a on OpenAPI 2.0 server In
with REST endpoints. The server builds and responds to queries, but [Part One of Go-Swagger](http://www.elfsternberg.com/2018/03/30/writing-microservice-swagger-part-1-specification/),
every valid query ends with "This feature has not yet been we generated a on OpenAPI 2.0 server with REST endpoints. The server
implemented." builds and responds to queries, but every valid query ends with "This
feature has not yet been implemented."
It's time to implement the feature. It's time to implement the feature.
I want to emphasize that with Go Swagger there is *only* one generated I want to emphasize that with Go Swagger there is *only* one generated
file you need to touch. Since our project is named `timezone`, the file you need to touch. Since our project is named `timeofday`, the
file will be named `restapi/configure_timezone.go`. Our first step file will be named `restapi/configure_timeofday.go`. Our first step
will be to break those "not implemented" functions out into their own will be to break those "not implemented" functions out into their own
Go package. That package will be our business logic. The configure Go package. That package will be our business logic. The configure
file and the business logic package will be the *only* things we file and the business logic package will be the *only* things we
change. change.
A reminder: The final source code for this project is available on
Github, however Parts One & Two deal with the most common
implementation, a server with hard-coded default values. For these
chapters, please consult
[that specific version of the code](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0).
## Break out the business logic ## Break out the business logic
Create a new folder in your project root and call it `timeofday`. Create a new folder in your project root and call it `timeofday`.
Open up your editor and find the file `restapi/configure_timeofday.go`. Open up your editor and find the file `restapi/configure_timeofday.go`.
In your `swagger.yml` file you created two endpoints and gave them each In your `swagger.yml` file you created two endpoints and gave them each
an `operationId`: `TimekPost` and `TimeGet`. Inside an `operationId`: `TimePost` and `TimeGet`. Inside
`configure_timeofday.go`, you should find two corresponding assignments `configure_timeofday.go`, you should find two corresponding assignments
in the function `configureAPI()`: `TimeGetHandlerFunc` and in the function `configureAPI()`: `TimeGetHandlerFunc` and
`ClockPostHandlerFunc`. Inside those function calls, you'll find `TimePostHandlerFunc`. Inside those function calls, you'll find
anonymous functions. anonymous functions.
I want you to take those anonymous functions, cut them out, and paste I want you to take those anonymous functions, cut them out, and paste
them into a new file inside the `timeofday/` folder. You will also have them into a new file inside the `timeofday/` folder. You will also have
to create a package name and import any packages being used. Now your to create a package name and import any packages being used. Now your
file, which I've called `timeofday/handlers.go`, looks like this: file, which I've called `timeofday/handlers.go`, looks like this (note
that you'll have to change your import paths as you're probably not
elfsternberg. Heck, _I'm_ probably not elfsternberg):
``` ```
<<handlers.go before implementation>>= <<handlers.go before implementation>>=
@ -155,7 +164,7 @@ The good path is similar:
Now might be a good time to go look in `models/` and `/restapi/options`, Now might be a good time to go look in `models/` and `/restapi/options`,
to see what's available to you. You'll need to do so anyway, because to see what's available to you. You'll need to do so anyway, because
unless you go to the unless you go to the
[git repository](https://github.com/elfsternberg/go-swagger-tutorial) [git repository](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0)
and cheat, I'm going to leave it up to you to implement the PostTime(). and cheat, I'm going to leave it up to you to implement the PostTime().
There's still one thing missing, though: the actual time of day. We'll There's still one thing missing, though: the actual time of day. We'll
@ -205,5 +214,9 @@ $ curl 'http://localhost:8020/timeofday/v1/time?timezone=America/Los_Angeles'
``` ```
And that's the end of Part 2. If you've gotten this far, And that's the end of Part 2. If you've gotten this far,
congratulations! On to [Part 3](TK:). congratulations! Just a reminder, a working version of this server is
available under the "0.2.0" tag
[at the repo](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0).
On to [Part 3](TK)

View File

@ -1,17 +1,18 @@
# Adding Command Line Arguments to the Swagger Server The first two parts of my swagger tutorial
[[Part 1](http://www.elfsternberg.com/2018/03/30/writing-microservice-swagger-part-1-specification/),
[Part 2](http://www.elfsternberg.com/2018/03/30/writing-microservice-swagger-part-2-business-logic/)]
were dedicated to the straightforward art of getting swagger up and
running. While I hope they're helpful, the whole point of those was to
get you to the point where you had the Timezone project, so I could show
you how to add Command Line Arguments to a Swagger microservice.
The first two parts of my swagger tutorial were dedicated to the One thing that I emphasized in
straightforward art of getting swagger up and running. While I hope [Go Swagger Part 2](http://www.elfsternberg.com/2018/03/30/writing-microservice-swagger-part-2-business-logic/)
they're helpful, the whole point of those was to get you to the point was that `configure_timeofday.go` is the *only* file you should be
where you had the Timezone project, so I could teach how to extend the touching, it's the interface between the server and your business logic.
Command Line Arguments of a swagger project. Every example of adding new flags to the command line, even
[the one provided by the GoSwagger authors](https://github.com/go-openapi/kvstore/blob/master/cmd/kvstored/main.go#L50-L57),
Nobody else, so far as I know, knows how to do this. One thing that I starts by modifying the file `cmd/<project>-server/main.go`, one of
emphasized in [Go Swagger Part 2](TK:) was that `configure_timeofday.go`
is the *only* file you should be touching, it's the interface between
the server and your business logic. Every example of adding new flags
to the command line, even the ones provided by the GoSwagger authors,
starts by modifying the file `cmd/\<project\>-server/main.go`, one of
those files clearly marked `// DO NOT EDIT`. those files clearly marked `// DO NOT EDIT`.
We're not going to edit files marked `// DO NOT EDIT`. We're not going to edit files marked `// DO NOT EDIT`.
@ -23,7 +24,7 @@ uses for handling command line arguments, `go-flags`.
`go-flags` is the tool Swagger uses by default for handling command line `go-flags` is the tool Swagger uses by default for handling command line
arguments. It's a clever tool that uses Go's arguments. It's a clever tool that uses Go's
[tags and retrospection](TK:) features to encade the details of the CLI [tags and reflection](https://golang.org/pkg/reflect/) features to encade the details of the CLI
directly into a structure that will hold the options passed in on the directly into a structure that will hold the options passed in on the
command line. command line.
@ -129,9 +130,17 @@ do we set the context?
The *correct* way is to modify the handlers so they have the context The *correct* way is to modify the handlers so they have the context
when they're called upon. The way we do that is via the oldest when they're called upon. The way we do that is via the oldest
object-oriented technique of all time, one that dates all the way back object-oriented technique of all time, one that dates all the way back
to 1959 and the invention of Lisp: *closures*. to 1964 and the invention of Lisp:
*[closures](https://tour.golang.org/moretypes/25)*. A closure *wraps*
one or more functions in an environment (a collection of variables
outside those functions), and preserves handles to those variables even
when those functions are passed out of the environment as references. A
garbage-collected language like Go makes this an especially powerful
technique because it means that anything in the environment for which
you *don't* keep handles will get collected, leaving only what matters.
Remember these lines in `configure_timeofday.go`, from way back? So, let's do it. Remember these lines in `configure_timeofday.go`, from
way back?
``` ```
api.TestGetHandler = operations.TestGetHandlerFunc(func(params operations.TestGetParams) middleware.Responder { api.TestGetHandler = operations.TestGetHandlerFunc(func(params operations.TestGetParams) middleware.Responder {
@ -151,24 +160,25 @@ replace those lines, *again*, so they look like this:
Those are no longer references to functions. They're *function calls*! Those are no longer references to functions. They're *function calls*!
What do those functions return? Well, we know TimeGetHandlerFunc() is What do those functions return? Well, we know TimeGetHandlerFunc() is
expecting a function, so so that function call had better return a expecting a referece to a function, so so that function call had better
function. return a reference to a function.
And indeed it does. And indeed it does:
``` ```
func GetTime(timezone *Timezone) func(operations.TimeGetParams) middleware.Responder{ func GetTime(timezone *Timezone) func(operations.TimeGetParams) middleware.Responder{
defaultTZ := timezone.Timezone defaultTZ := timezone.Timezone
// Here's the function we return:
return func(params operations.TimeGetParams) middleware.Responder { return func(params operations.TimeGetParams) middleware.Responder {
// Everything else is the same... except we need *two* levels of // Everything else is the same... except we need *two* levels of
// closing } at the end! // closing } at the end!
``` ```
Now, instead of returning a function defined at compile time, we Now, instead of returning a function defined at compile time, we
returned a function that is finalized when GetTime() is called, and it returned a function reference that is finalized when GetTime() is
now holds a permanent reference to our Timezone object. Do the same called, and it now holds a permanent reference to our Timezone object.
thing for `PostTime`. Do the same thing for `PostTime`.
There's one more thing we have to do. We've moved our default timezone There's one more thing we have to do. We've moved our default timezone
to the `configure_timeofday.go` file, so we don't need it here anymore: to the `configure_timeofday.go` file, so we don't need it here anymore:
@ -190,8 +200,31 @@ And that's it. That's everything. You can add all the command line
arguments you want, and only preserve the fields that are relevant to arguments you want, and only preserve the fields that are relevant to
the particular handler you're going to invoke. the particular handler you're going to invoke.
You can now build and run the server, but with a command line:
```
$ go build ./cmd/timeofday-server/
$ ./timeofday-server --port=8080 --timezone="America/Los_Angeles"
```
And test it with curl:
```
$ curl 'http://localhost:8020/timeofday/v1/time?timezone=America/New_York'
{"timeofday":"2018-03-30 23:44:47.701895604 -0400 EDT"}
$ curl 'http://localhost:8020/timeofday/v1/time'
{"timeofday":"2018-03-30 20:44:54.525313806 -0700 PDT"}
```
Note that the default timezone is now PDT, or Pacific Daily Time, which
corresponds to the America/Los_Angeles entry in the database in late
March.
And *that's* how you add command line arguments to Swagger servers And *that's* how you add command line arguments to Swagger servers
correctly. correctly without exposing your CLI settings to every other function in
your server. If you want to see the entirely of the source code, the
[advanced version on the repository](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.4.0)
has it all.