Cara untuk mengimplementasikan server Golang API dengan kode dan dokumentasi yang dibuat secara otomatis

Pada artikel ini saya ingin memberi tahu Anda tentang bagaimana Anda dapat dengan cepat dan mudah membuat server web dalam bahasa Golang dengan dokumentasi untuknya. Dan tentang apa saja pendekatan dan alat untuk implementasinya


Hari ini kita akan menganalisis alat yang sudah jadi ini:



Sombong codegen


Mari kita mulai dengan swagger-api / swagger-codegen yang disediakan oleh swagger.io . Swagger Codegen menyederhanakan proses pembuatan dengan membuat stubs server dan SDK klien untuk API apa pun yang ditentukan dalam spesifikasi OpenAPI (sebelumnya dikenal sebagai Swagger), yang harus Anda lakukan adalah menerapkan API Anda.


Di bawah ini adalah contoh file swagger kami dan tampilan visualnya di Swagger Editor, di mana kami dapat menghasilkan implementasi kami untuk satu hal.


swagger.json
{
  "swagger" : "2.0",
  "info" : {
    "version" : "1.0.0",
    "title" : "Swagger Petstore"
  },
  "host" : "localhost",
  "basePath" : "/v1",
  "tags" : [ {
    "name" : "pet"
  } ],
  "schemes" : [ "https", "http" ],
  "paths" : {
    "/pet" : {
      "post" : {
        "tags" : [ "pet" ],
        "summary" : "Add a new pet to the store",
        "operationId" : "addPet",
        "consumes" : [ "application/json" ],
        "produces" : [ "application/json" ],
        "parameters" : [ {
          "in" : "body",
          "name" : "body",
          "description" : "Pet object that needs to be added to the store",
          "required" : true,
          "schema" : {
            "$ref" : "#/definitions/Pet"
          }
        } ],
        "responses" : {
          "405" : {
            "description" : "Invalid input"
          }
        }
      }
    }
  },
  "definitions" : {
    "Category" : {
      "type" : "object",
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "name" : {
          "type" : "string"
        }
      }
    },
    "Tag" : {
      "type" : "object",
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "name" : {
          "type" : "string"
        }
      }
    },
    "Pet" : {
      "type" : "object",
      "required" : [ "name", "photoUrls" ],
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "category" : {
          "$ref" : "#/definitions/Category"
        },
        "name" : {
          "type" : "string",
          "example" : "doggie"
        },
        "photoUrls" : {
          "type" : "array",
          "items" : {
            "type" : "string"
          }
        },
        "tags" : {
          "type" : "array",
          "items" : {
            "$ref" : "#/definitions/Tag"
          }
        },
        "status" : {
          "type" : "string",
          "description" : "pet status in the store",
          "enum" : [ "available", "pending", "sold" ]
        }
      }
    }
  }
}

Tampilan visual di UI Sombong

Tampilan visual di UI Sombong


Ekspor dari Editor Swagger

Ekspor dari Editor Swagger


Klien dan server memiliki objek struktur yang identik. File contoh go-client-generated/model_pet.godan go-server-server-generated/go/model_pet.go.


Model struktur hewan peliharaan di klien dan server
/*
 * Swagger Petstore
 *
 * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
 *
 * API version: 1.0.0
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package swagger

type Pet struct {
    Id int64           `json:"id,omitempty"`
    Category *Category `json:"category,omitempty"`
    Name string        `json:"name"`
    PhotoUrls []string `json:"photoUrls"`
    Tags []Tag         `json:"tags,omitempty"`
    // pet status in the store
    Status string `json:"status,omitempty"`
}

Klien juga berisi metode untuk mengirim permintaan, dan endpionts dan stub server untuk memprosesnya.


Arsitektur pelanggan

Arsitektur pelanggan


Arsitektur server


, . , .


go-swagger


β€Š β€” β€Šgo-swagger/go-swagger. go API- Swagger API: , .


  • Swagger
  • Swagger
  • , jsonschema swagger,
  • swagger go
  • swagger
  • ,


:


$ swagger generate server -f ./swagger.json

 

 


, , . .


β€” go-swagger-service/restapi/configure_swagger_petstore.go. : handler', middleware, , , .


// This file is safe to edit. Once it exists it will not be overwritten

package restapi

import (
    "crypto/tls"
    "net/http"

    errors "github.com/go-openapi/errors"
    runtime "github.com/go-openapi/runtime"
    middleware "github.com/go-openapi/runtime/middleware"

    "go-generator/go-swagger-service/restapi/operations"
    "go-generator/go-swagger-service/restapi/operations/pet"
)

//go:generate swagger generate server --target ../../go-swagger --name SwaggerPetstore --spec ../../swagger.json

func configureFlags(api *operations.SwaggerPetstoreAPI) {
    // api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... }
}

func configureAPI(api *operations.SwaggerPetstoreAPI) http.Handler {
    // configure the api here
    api.ServeError = errors.ServeError

    // Set your custom logger if needed. Default one is log.Printf
    // Expected interface func(string, ...interface{})
    //
    // Example:
    // api.Logger = log.Printf

    api.JSONConsumer = runtime.JSONConsumer()

    api.JSONProducer = runtime.JSONProducer()

    if api.PetAddPetHandler == nil {
        api.PetAddPetHandler = pet.AddPetHandlerFunc(func(params pet.AddPetParams) middleware.Responder {
            return middleware.NotImplemented("operation pet.AddPet has not yet been implemented")
        })
    }

    api.ServerShutdown = func() {}

    return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}

// The TLS configuration before HTTPS server starts.
func configureTLS(tlsConfig *tls.Config) {
    // Make all necessary changes to the TLS configuration here.
}

// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix"
func configureServer(s *http.Server, scheme, addr string) {
}

// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation
func setupMiddlewares(handler http.Handler) http.Handler {
    return handler
}

// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
    return handler
}

. 422 .


swagger . enum, required, type, maximum .


Pet
// Code generated by go-swagger; DO NOT EDIT.

package models

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
    "encoding/json"
    "strconv"

    strfmt "github.com/go-openapi/strfmt"

    "github.com/go-openapi/errors"
    "github.com/go-openapi/swag"
    "github.com/go-openapi/validate"
)

// Pet pet
// swagger:model Pet
type Pet struct {

    // category
    Category *Category `json:"category,omitempty"`

    // id
    ID int64 `json:"id,omitempty"`

    // name
    // Required: true
    Name *string `json:"name"`

    // photo urls
    // Required: true
    PhotoUrls []string `json:"photoUrls"`

    // pet status in the store
    // Enum: [available pending sold]
    Status string `json:"status,omitempty"`

    // tags
    Tags []*Tag `json:"tags"`
}

// Validate validates this pet
func (m *Pet) Validate(formats strfmt.Registry) error {
    var res []error

    if err := m.validateCategory(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validateName(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validatePhotoUrls(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validateStatus(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validateTags(formats); err != nil {
        res = append(res, err)
    }

    if len(res) > 0 {
        return errors.CompositeValidationError(res...)
    }
    return nil
}

func (m *Pet) validateCategory(formats strfmt.Registry) error {

    if swag.IsZero(m.Category) { // not required
        return nil
    }

    if m.Category != nil {
        if err := m.Category.Validate(formats); err != nil {
            if ve, ok := err.(*errors.Validation); ok {
                return ve.ValidateName("category")
            }
            return err
        }
    }

    return nil
}

func (m *Pet) validateName(formats strfmt.Registry) error {

    if err := validate.Required("name", "body", m.Name); err != nil {
        return err
    }

    return nil
}

func (m *Pet) validatePhotoUrls(formats strfmt.Registry) error {

    if err := validate.Required("photoUrls", "body", m.PhotoUrls); err != nil {
        return err
    }

    return nil
}

var petTypeStatusPropEnum []interface{}

func init() {
    var res []string
    if err := json.Unmarshal([]byte(`["available","pending","sold"]`), &res); err != nil {
        panic(err)
    }
    for _, v := range res {
        petTypeStatusPropEnum = append(petTypeStatusPropEnum, v)
    }
}

const (

    // PetStatusAvailable captures enum value "available"
    PetStatusAvailable string = "available"

    // PetStatusPending captures enum value "pending"
    PetStatusPending string = "pending"

    // PetStatusSold captures enum value "sold"
    PetStatusSold string = "sold"
)

// prop value enum
func (m *Pet) validateStatusEnum(path, location string, value string) error {
    if err := validate.Enum(path, location, value, petTypeStatusPropEnum); err != nil {
        return err
    }
    return nil
}

func (m *Pet) validateStatus(formats strfmt.Registry) error {

    if swag.IsZero(m.Status) { // not required
        return nil
    }

    // value enum
    if err := m.validateStatusEnum("status", "body", m.Status); err != nil {
        return err
    }

    return nil
}

func (m *Pet) validateTags(formats strfmt.Registry) error {

    if swag.IsZero(m.Tags) { // not required
        return nil
    }

    for i := 0; i < len(m.Tags); i++ {
        if swag.IsZero(m.Tags[i]) { // not required
            continue
        }

        if m.Tags[i] != nil {
            if err := m.Tags[i].Validate(formats); err != nil {
                if ve, ok := err.(*errors.Validation); ok {
                    return ve.ValidateName("tags" + "." + strconv.Itoa(i))
                }
                return err
            }
        }

    }

    return nil
}

// MarshalBinary interface implementation
func (m *Pet) MarshalBinary() ([]byte, error) {
    if m == nil {
        return nil, nil
    }
    return swag.WriteJSON(m)
}

// UnmarshalBinary interface implementation
func (m *Pet) UnmarshalBinary(b []byte) error {
    var res Pet
    if err := swag.ReadJSON(b, &res); err != nil {
        return err
    }
    *m = res
    return nil
}


$ run ./go-swagger-service/cmd/swagger-petstore-server/main.go --port 8090


:


$ swagger generate client -f ./swagger.json

 

 


, api-sdk. , , .


// Code generated by go-swagger; DO NOT EDIT.

package pet

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
    "github.com/go-openapi/runtime"

    strfmt "github.com/go-openapi/strfmt"
)

// New creates a new pet API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
    return &Client{transport: transport, formats: formats}
}

/*
Client for pet API
*/
type Client struct {
    transport runtime.ClientTransport
    formats   strfmt.Registry
}

/*
AddPet adds a new pet to the store
*/
func (a *Client) AddPet(params *AddPetParams) error {
    // TODO: Validate the params before sending
    if params == nil {
        params = NewAddPetParams()
    }

    _, err := a.transport.Submit(&runtime.ClientOperation{
        ID:                 "addPet",
        Method:             "POST",
        PathPattern:        "/pet",
        ProducesMediaTypes: []string{"application/json"},
        ConsumesMediaTypes: []string{"application/json"},
        Schemes:            []string{"http", "https"},
        Params:             params,
        Reader:             &AddPetReader{formats: a.formats},
        Context:            params.Context,
        Client:             params.HTTPClient,
    })
    if err != nil {
        return err
    }
    return nil
}

// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
    a.transport = transport
}

. Generate a spec from source code.


. , swagger .


grpc-gateway


swagger . grpc-ecosystem/grpc-gateway β€” β€Š protoc. gRPC -, RESTful JSON API gRPC. gRPC.


HTTP + JSON gRPC. API gRPC RESTful .


grpc-gateway reverse-proxy

grpc-gateway   reverse-proxy


swagger.json protoc-gen-swagger.


. .


$ go install \
    github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway \
    github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger \
    github.com/golang/protobuf/protoc-gen-go

 .proto


protobuf
syntax = "proto3";

import "google/api/annotations.proto";
import "google/protobuf/empty.proto";

package pet;

message AddPetRequest {
    int64 id = 1;
    message Category {
        int64 id = 1;
        string name = 2;
    }
    Category category = 2;
    string name = 3;
    repeated string photo_urls = 4;
    message Tag {
        int64 id = 1;
        string name = 2;
    }
    repeated Tag tags = 5;
    //pet status in the store
    enum Status {
        available = 0;
        pending = 1;
        sold = 2;
    }
    Status status = 6;
}

service PetService {
    rpc AddPet (AddPetRequest) returns (google.protobuf.Empty) {
        option (google.api.http) = {
            post: "/pet"
            body: "*"
        };
    }
}

gRPC stub:


$ protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --go_out=plugins=grpc:. \
  pet.proto

pet.pb.go. reverse-proxy protoc-gen-grpc-gateway:


$ protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --grpc-gateway_out=logtostderr=true:. \
  pet.proto

pet.pb.gw.go . gateway. pet.swagger.json protoc-gen-swagger:


$ protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --swagger_out=logtostderr=true:. \
  pet.proto

PetServiceServer.


// PetServiceServer is the server API for PetService service.
type PetServiceServer interface {
   AddPet(context.Context, *AddPetRequest) (*empty.Empty, error)
}

:


main.go
package pet

import (
    "context"
    "flag"
    "net/http"

    "github.com/golang/glog"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    gw "grpc-gateway/pet"
    "grpc-gateway/service"
)

func run() error {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    mux := runtime.NewServeMux()
    err := gw.RegisterPetServiceHandlerServer(ctx, mux, &service.PetService{})
    if err != nil {
        return err
    }

    return http.ListenAndServe(":8081", mux)
}

func main() {
    flag.Parse()
    defer glog.Flush()

    if err := run(); err != nil {
        glog.Fatal(err)
    }
}

, . , c grpc-gateway


Swag


Swag Go Swagger 2.0. - Go. swaggo/swag Go ( Swagger UI).


:



Swagger , endpoint'. .


API
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// @BasePath /api/v1
// @query.collection.format multi

// @securityDefinitions.basic BasicAuth

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information

// @x-extension-openapi {"example": "value on a json format"}

func main() {
    // ... 
}

API-
// ShowAccount godoc
// @Summary Show a account
// @Description get string by ID
// @ID get-string-by-int
// @Accept  json
// @Produce  json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Header 200 {string} Token "qwerty"
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [get]

func (c *ExampleController) ShowAccount(ctx *contex.Context) {
    // ...
}

github.


, , .



Sekarang kami baru saja memperluas pemahaman kami tentang kemungkinan interaksi dengan dokumentasi kesombongan. Kami memeriksa berbagai cara untuk merancang kode dan menyusun dokumentasi untuknya.


Dan saya pikir beberapa telah memilih metode yang tepat untuk mengimplementasikan API dari proyek baru mereka, atau mencoba contoh yang dianalisis terhadap yang sekarang.


All Articles