$30 off During Our Annual Pro Sale. View Details »

Go-Swagger в продуктиве

Go-Swagger в продуктиве

Ilya Kaznacheev

May 30, 2020
Tweet

More Decks by Ilya Kaznacheev

Other Decks in Programming

Transcript

  1. Go-Swagger
    в продуктиве
    взлеты и падения

    View Slide

  2. Илья Казначеев
    Remote Backend SWE
    Основатель Golang Voronezh
    Ведущий подкаста Z-Namespace
    Организатор конференций и митапов
    Любитель кофе

    View Slide

  3. Golang Voronezh
    - ~30 активных участников
    - митапы
    - мероприятия для новичков
    - открыто и дружелюбно
    t.me/golang_vrn
    meetup.com/Golang-Voronezh

    View Slide

  4. что такое swagger?

    View Slide

  5. View Slide

  6. SOAP
    JSON-PRC
    GraphQL
    gRPC
    OData
    REST

    View Slide

  7. Representational state transfer (REST)
    is a software architectural style that defines
    a set of constraints to be used
    for creating Web services
    Wikipedia

    View Slide

  8. View Slide

  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

    View Slide

  10. View Slide

  11. View Slide

  12. почему мы
    используем swagger?

    View Slide

  13. команда синхронизирует изменения в API...

    View Slide

  14. View Slide

  15. библиотеки
    go-swagger/go-swagger ✭ 5.2 K
    swaggo/swag ✭ 2.7 K
    deepmap/oapi-codegen ✭ 440

    grpc-ecosystem/grpc-gateway ✭ 8.5 K

    View Slide

  16. go-swagger

    View Slide

  17. генерация кода
    swagger generate server -t internal/api --exclude-main

    View Slide

  18. структура сгенерированных файлов
    internal/api
    ├ models
    │ └ ...
    └ restapi
    ├ operations
    │ └ ...
    ├ configure_.go
    ├ doc.go
    ├ embedded_spec.go
    └ server.go

    View Slide

  19. как мы генерируем код
    rm -rf internal/api && mkdir -p internal/api
    swagger generate server -t internal/api --exclude-main
    go mod tidy

    View Slide

  20. ?
    вроде все просто

    View Slide

  21. НЕТ

    View Slide

  22. есть некоторые проблемы
    - go-swagger - это фреймворк, а не библиотека
    - куча сгенерированных типов на каждый чих
    - несовместим с популярными http библиотеками

    View Slide

  23. давайте фиксить!

    View Slide

  24. net/http хендлеры
    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)
    })
    }

    View Slide

  25. простые middleware
    api := operations.NewSwaggerPetstoreAPI(swaggerSpec)
    api.InstrumentsGetMetricsHandler =
    instruments.GetMetricsHandlerFunc(MetricsHandler)
    api.AddMiddlewareFor("GET", "/metrics", SomeMiddleware)
    srv := restapi.NewServer(api)
    srv.Serve()

    View Slide

  26. middleware с кастомным хендлером
    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()

    View Slide

  27. настройки вне configure_.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

    View Slide

  28. кастомные имена методов
    /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)
    }

    View Slide

  29. кастомные имена методов
    /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)
    }

    View Slide

  30. встроенная валидация
    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

    View Slide

  31. шпаргалка по валидации
    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

    View Slide

  32. расширения (костыли)
    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

    View Slide

  33. юнит-тесты
    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)
    }

    View Slide

  34. юнит-тесты
    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}`,
    },
    }

    View Slide

  35. юнит-тесты
    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")
    })
    }

    View Slide

  36. View Slide

  37. полезные ссылки
    json-schema.org/specification.html
    swagger.io/docs/specification/2-0
    goswagger.io
    bit.ly/go-swagger-in-production

    View Slide

  38. бонус
    A pluggable go-swagger (in development)
    github.com/ilyakaznacheev/go-plugger

    View Slide

  39. бонус 2
    Insomnia Designer
    insomnia.rest/products/designer

    View Slide

  40. View Slide

  41. ilyakaznacheev

    View Slide