Documentation done, and Readme annotated with the appropriate links.
This commit is contained in:
parent
406fee5458
commit
0237bc1d8a
|
@ -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
|
||||
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
|
||||
ndpoints for a webserver's API, usually a REST-based API. HTTP uses
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
3. Glue the business logic into the server generated from the
|
||||
specification.
|
||||
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
|
||||
|
@ -64,6 +81,7 @@ $ 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
|
||||
|
@ -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:
|
||||
|
||||
```
|
||||
<<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:
|
||||
|
@ -133,11 +154,13 @@ paths:
|
|||
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
|
||||
The octothorpe symbol `(#)` indicates "look in the current file." So
|
||||
now we have to create those paths:
|
||||
|
||||
```
|
||||
<<schemas>>=
|
||||
definitions:
|
||||
NotFound:
|
||||
|
@ -158,6 +181,7 @@ definitions:
|
|||
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
|
||||
|
@ -165,6 +189,7 @@ with.
|
|||
|
||||
So now your file looks like this:
|
||||
|
||||
```
|
||||
<<swagger.yml>>=
|
||||
swagger: "2.0"
|
||||
info:
|
||||
|
@ -186,14 +211,19 @@ schemes:
|
|||
|
||||
<<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.
|
||||
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`
|
||||
|
||||
|
@ -224,4 +254,4 @@ $ curl http://localhost:8082/timeofday/v1/time
|
|||
Congratulations! You have a working REST server that does, well,
|
||||
nothing.
|
||||
|
||||
For part two, we'll make our server actually do things.
|
||||
For Part 2, we'll make our server actually do things.
|
||||
|
|
|
@ -1,36 +1,45 @@
|
|||
# Review of Part One
|
||||
|
||||
In [Part One of Go-Swagger](TK:), we generated a on OpenAPI 2.0 server
|
||||
with REST endpoints. The server builds and responds to queries, but
|
||||
every valid query ends with "This feature has not yet been
|
||||
implemented."
|
||||
In
|
||||
[Part One of Go-Swagger](http://www.elfsternberg.com/2018/03/30/writing-microservice-swagger-part-1-specification/),
|
||||
we generated a on OpenAPI 2.0 server with REST endpoints. The server
|
||||
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.
|
||||
|
||||
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 will be named `restapi/configure_timezone.go`. Our first step
|
||||
file you need to touch. Since our project is named `timeofday`, the
|
||||
file will be named `restapi/configure_timeofday.go`. Our first step
|
||||
will be to break those "not implemented" functions out into their own
|
||||
Go package. That package will be our business logic. The configure
|
||||
file and the business logic package will be the *only* things we
|
||||
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
|
||||
|
||||
Create a new folder in your project root and call it `timeofday`.
|
||||
|
||||
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
|
||||
an `operationId`: `TimekPost` and `TimeGet`. Inside
|
||||
an `operationId`: `TimePost` and `TimeGet`. Inside
|
||||
`configure_timeofday.go`, you should find two corresponding assignments
|
||||
in the function `configureAPI()`: `TimeGetHandlerFunc` and
|
||||
`ClockPostHandlerFunc`. Inside those function calls, you'll find
|
||||
`TimePostHandlerFunc`. Inside those function calls, you'll find
|
||||
anonymous functions.
|
||||
|
||||
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
|
||||
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>>=
|
||||
|
@ -155,7 +164,7 @@ The good path is similar:
|
|||
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
|
||||
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().
|
||||
|
||||
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,
|
||||
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)
|
||||
|
||||
|
|
|
@ -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
|
||||
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 teach how to extend the
|
||||
Command Line Arguments of a swagger project.
|
||||
|
||||
Nobody else, so far as I know, knows how to do this. One thing that I
|
||||
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
|
||||
One thing that I emphasized in
|
||||
[Go Swagger Part 2](http://www.elfsternberg.com/2018/03/30/writing-microservice-swagger-part-2-business-logic/)
|
||||
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 one provided by the GoSwagger authors](https://github.com/go-openapi/kvstore/blob/master/cmd/kvstored/main.go#L50-L57),
|
||||
starts by modifying the file `cmd/<project>-server/main.go`, one of
|
||||
those files clearly 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
|
||||
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
|
||||
command line.
|
||||
|
||||
|
@ -129,9 +130,17 @@ do we set 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
|
||||
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 {
|
||||
|
@ -151,24 +160,25 @@ replace those lines, *again*, so they look like this:
|
|||
|
||||
Those are no longer references to functions. They're *function calls*!
|
||||
What do those functions return? Well, we know TimeGetHandlerFunc() is
|
||||
expecting a function, so so that function call had better return a
|
||||
function.
|
||||
expecting a referece to a function, so so that function call had better
|
||||
return a reference to a function.
|
||||
|
||||
And indeed it does.
|
||||
And indeed it does:
|
||||
|
||||
```
|
||||
func GetTime(timezone *Timezone) func(operations.TimeGetParams) middleware.Responder{
|
||||
defaultTZ := timezone.Timezone
|
||||
|
||||
// Here's the function we return:
|
||||
return func(params operations.TimeGetParams) middleware.Responder {
|
||||
// Everything else is the same... except we need *two* levels of
|
||||
// closing } at the end!
|
||||
// Everything else is the same... except we need *two* levels of
|
||||
// closing } at the end!
|
||||
```
|
||||
|
||||
Now, instead of returning a function defined at compile time, we
|
||||
returned a function that is finalized when GetTime() is called, and it
|
||||
now holds a permanent reference to our Timezone object. Do the same
|
||||
thing for `PostTime`.
|
||||
returned a function reference that is finalized when GetTime() is
|
||||
called, and it now holds a permanent reference to our Timezone object.
|
||||
Do the same thing for `PostTime`.
|
||||
|
||||
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:
|
||||
|
@ -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
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue