Go-Swagger in production

Go-Swagger in production

Cee15b0246090c00f7de03e0a976f3ed?s=128

Ilya Kaznacheev

May 21, 2020
Tweet

Transcript

  1. Go-Swagger in production wins and pitfalls

  2. Ilya Kaznacheev Remote Backend SWE Founder of Golang Voronezh Host

    of Z-Namespace podcast Organizer of conference and meetups Coffee geek
  3. Golang Voronezh - ~30 active members - meetups - events

    for beginners - open & friendly
  4. what swagger is?

  5. None
  6. SOAP JSON-PRC GraphQL gRPC OData REST

  7. Representational state transfer (REST) is a software architectural style that

    defines a set of constraints to be used for creating Web services Wikipedia
  8. None
  9. swagger: "2.0" info: title: Pet API version: "1.0.0" basePath: /api

    schemes: - http paths: /pets: get: summary: List all pets parameters: - name: limit in: query description: "How many items to return at one time" required: true type: integer responses: 200: description: an paged array of pets 400: description: unexpected error
  10. None
  11. None
  12. why do we use swagger?

  13. my team trying to sync API changes...

  14. None
  15. go-swagger

  16. code generation swagger generate server -t internal/api --exclude-main

  17. generated code structure internal/api ├ models │ └ ... └

    restapi ├ operations │ └ ... ├ configure_<your_service_name>.go ├ doc.go ├ embedded_spec.go └ server.go
  18. our code generation rm -rf internal/api && mkdir -p internal/api

    swagger generate server -t internal/api --exclude-main go mod tidy
  19. and we're all set ?

  20. NO

  21. there are some problems - go-swagger is a framework, not

    a library - plenty of generated types for everything - incompatible with popular http-libraries
  22. let’s fix ’em all!

  23. serving net/http handlers type CustomResponder func(http.ResponseWriter, runtime.Producer) func (c CustomResponder)

    WriteResponse(w http.ResponseWriter, p runtime.Producer) { c(w, p) } func MetricsHandler(p instruments.GetMetricsParams) middleware.Responder { return CustomResponder(func(w http.ResponseWriter, _ runtime.Producer) { promhttp.Handler().ServeHTTP(w, p.HTTPRequest) }) }
  24. simple middleware api := operations.NewSwaggerPetstoreAPI(swaggerSpec) api.InstrumentsGetMetricsHandler = instruments.GetMetricsHandlerFunc(MetricsHandler) api.AddMiddlewareFor("GET", "/metrics",

    SomeMiddleware) srv := restapi.NewServer(api) srv.Serve()
  25. middleware with custom handler h := api.Serve(nil) r := chi.NewRouter()

    r.Use( middleware.Recoverer, ) r.With(AuthMiddleware).Group(func(r chi.Router) { r.Handle("/user/*", h) }) r.Mount("/", h) srv.ConfigureAPI() srv.SetHandler(r) srv.Serve()
  26. setup outside of configure_<your_service_name>.go api.Logger = log.Printf api.HTMLProducer = runtime.TextProducer()

    srv := restapi.NewServer(api) srv.EnabledListeners = []string{"http"} srv.Port = conf.HTTPPort srv.Host = conf.HTTPAddr
  27. custom method names /store/order/{orderId}/items: get: tags: - store summary: Find

    purchase order items parameters: - name: orderId in: path required: true type: integer func GetOrderItems( param store.GetStoreOrderOrderIDItemsParams, ) middleware.Responder { items, err := getOrderItems(param.OrderID) if err != nil { return store.NewGetStoreOrderOrderIDItemsNotFound() } res := &models.OrderItems{} // // fill resopnse // return store.NewGetStoreOrderOrderIDItemsOK(). WithPayload(res) }
  28. custom method names /store/order/{orderId}/items: get: tags: - store summary: Find

    purchase order items operationId: getOrderItems parameters: - name: orderId in: path required: true type: integer func GetOrderItems( param store.GetOrderItemsParams, ) middleware.Responder { items, err := getOrderItems(param.OrderID) if err != nil { return store.NewGetOrderItemsNotFound() } res := &models.OrderItems{} // // fill resopnse // return store.NewGetOrderItemsOK(). WithPayload(res) }
  29. validity checks OrderItems: type: object properties: message: type: string maximum:

    3 # swg/internal/api/models internal/api/models/order_items.go:45:55: cannot convert m.Message (type string) to type float64
  30. validity check cheat sheet numbers and integers - multipleOf -

    maximum - minimum - exclusiveMaximum - exclusiveMinimum strings - maxLength - minLength - pattern arrays - maxItems - minItems - uniqueItems - maxContains - minContains objects - maxProperties - minProperties - required - dependentRequired any type - type - enum - const
  31. extensions (tricks) x-omitempty x-nullable x-isnullable x-order x-go-custom-tag x-schemes x-go-name x-go-type

    x-go-json-string x-go-enum-ci
  32. unit tests func GetOrderByID(param store.GetOrderByIDParams) middleware.Responder { order := models.Order{

    ID: 123, PetID: 456, Quantity: 20, Status: "approved", } if param.OrderID != order.ID { return store.NewGetOrderByIDNotFound().WithPayload(&models.ErrorMessage{ Code: http.StatusNotFound, Message: http.StatusText(http.StatusNotFound), }) } return store.NewGetOrderByIDOK().WithPayload(&order) }
  33. unit tests tests := []struct { name string req store.GetOrderByIDParams

    code int want string }{ { name: "good test", req: store.GetOrderByIDParams{OrderID: 123}, code: 200, want: `{"id":123,"petId":456,"quantity":20,"status":"approved"}`, }, { name: "bad test", req: store.GetOrderByIDParams{OrderID: 456}, code: 404, want: `{"message":"Not Found", "code":404}`, }, }
  34. unit tests for _, tt := range tests { t.Run(tt.name,

    func(t *testing.T) { rr := httptest.NewRecorder() GetOrderByID(tt.req).WriteResponse(rr, runtime.JSONProducer()) assert.JSONEq(t, tt.want, rr.Body.String(), "wrong response body") assert.Equal(t, tt.code, rr.Code, "wrong response code") }) }
  35. None
  36. helpful links json-schema.org/specification.html swagger.io/docs/specification/2-0 goswagger.io

  37. bonus A pluggable go-swagger (in development) github.com/ilyakaznacheev/go-plugger

  38. bonus 2 Insomnia Designer insomnia.rest/products/designer

  39. None
  40. ilyakaznacheev