Wouter Groeneveld fb1dff23b9 | ||
---|---|---|
docs | ||
pb | ||
pokemon | ||
rest | ||
.gitignore | ||
Makefile | ||
README.md | ||
go.mod | ||
go.sum | ||
main.go | ||
server.go |
README.md
Pokedex README
A simple Go-powered REST API kata.
HTTP Server
- KISS: Use built-in
http
package. https://gin-gonic.com/ looks cooler but also adds dozens of dependencies 😮 No need for a "fully-featured" web framework.
Protobuf
Why? Separation of concerns; do not expose database/domain internals. Structs in pokemon.go
should not leave the package. The .proto
file serve as the official "contracts".
- JSON mapping: https://protobuf.dev/programming-guides/proto3/#json
Pokemons
as object withentries
is ugly but see https://github.com/golang/protobuf/issues/675 - no way to convert a slice to a protobuf message...
Is this over-engineered or what? On top of that, protojson's performance is much worse:
goos: darwin
goarch: arm64
pkg: pokedex/rest
BenchmarkJson-8 1275931 928.9 ns/op
BenchmarkJson-8 1292184 926.6 ns/op
BenchmarkJson-8 1292647 926.0 ns/op
BenchmarkJson-8 1291228 926.3 ns/op
BenchmarkJson-8 1294453 927.3 ns/op
BenchmarkProtoJson-8 772567 1510 ns/op
BenchmarkProtoJson-8 766005 1517 ns/op
BenchmarkProtoJson-8 779281 1512 ns/op
BenchmarkProtoJson-8 774534 1513 ns/op
BenchmarkProtoJson-8 771370 1511 ns/op
PASS
ok pokedex/rest 16.500s
And that's with indent! Without:
BenchmarkJson-8 4067257 266.6 ns/op
BenchmarkJson-8 4503345 266.5 ns/op
BenchmarkJson-8 4512295 266.9 ns/op
BenchmarkJson-8 4505510 266.7 ns/op
BenchmarkJson-8 4482391 267.4 ns/op
The question then becomes: why use a .proto
file to exchange a contract at all, if you're not using gRPC?
Swagger
- Exposed at
http://localhost:8080/docs
- Regenerate with
swag init
, see https://github.com/swaggo/http-swagger - Annotation format: see https://github.com/swaggo/swag
Big bummer: annotations contain endpoint duplication...
Optimizing the binary
See Makefile
; use https://upx.github.io/ to package after stripping some debug info.
Somehow doesn't work on OSX (process killed)?
Interesting philosophical questions
- Where should routing be defined? Central or with the domain package (which doesn't seem to be the idiomatic Go way?)
- To use or not use DTO or response objects?
- How to handle dependencies without dragging in Yet Another Unneeded Framework?
- Custom error wrapping or not?
- Custom Go web frameworks that ease muxing and exponential back-offs or not?
- Where should db migration go? Reusability in tests?