设计优先呢?#
引言#
设计优先是一种 API 方法论,在编写任何代码之前先编写 API 规范。这通常与代码优先形成对比,在代码优先中,您先编写代码,然后从代码生成 API 规范。
graph LR
APISpec[API Spec] --> Review
Review --> APISpec
Review --> Code
Code --> Review2[Review]
Review2 --> Code
Review2 --> Deploy
优势#
设计优先让您在匆忙拼凑代码之前,先思考 API、其用例以及整体架构。它能实现对 API 设计的快速反馈和迭代。
虚假二分法#
幸运的是,“设计优先” vs. “代码优先”的二分法是虚假的。您可以同时进行两者!事实上,Huma 就是为了实现这一点而设计的,它提供了一种编写骨架代码的方法,而无需实现任何操作处理程序,从而可以独立于处理程序进行审查。
graph LR
HumaSkeleton[Huma Skeleton] --> Review
HumaSkeleton --> OpenAPI[OpenAPI Spec]
OpenAPI --> Review
Review --> HumaSkeleton
Review --> HumaImplementation[Huma Implementation]
HumaImplementation --> Review2[Review]
Review2 --> HumaImplementation
Review2 --> Deploy
示例#
以下是流行 Pet Store API 的一部分,以 Huma 的骨架形式编写,准备好进行审查和迭代,您可以通过审查代码或生成 API 规范并审查该规范来实现。
main.go
package main
import (
"context"
"fmt"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humachi"
"github.com/danielgtaylor/huma/v2/humacli"
"github.com/go-chi/chi/v5"
"github.com/spf13/cobra"
)
type Category struct {
ID int `json:"id" example:"1" doc:"类别 ID"`
Name string `json:"name" example:"Cats" doc:"类别名称"`
}
type Tag struct {
ID int `json:"id" example:"1" doc:"标签 ID"`
Name string `json:"name" example:"cute" doc:"标签名称"`
}
type Pet struct {
ID int `json:"id" example:"1" doc:"宠物 ID"`
Category *Category `json:"category" doc:"宠物所属的类别"`
Name string `json:"name" example:"Fluffy" doc:"宠物名称"`
PhotoURLs []string `json:"photoUrls" example:"https://example.com/fluffy.jpg" doc:"宠物的照片 URL"`
Tags []Tag `json:"tags" example:'["cute"]' doc:"宠物的标签"`
Status string `json:"status" example:"available" doc:"宠物状态" enum:"available,pending,sold"`
}
type PetID struct {
ID int `path:"petId" example:"1" doc:"宠物 ID"`
}
func main() {
var api huma.API
cli := humacli.New(func(hooks humacli.Hooks, options *struct{}) {
router := chi.NewMux()
api := humachi.New(router, huma.DefaultConfig("Pet Store", "1.0.0"))
huma.Register(api, huma.Operation{
OperationID: "post-pet",
Method: "POST",
Path: "/pet",
Summary: "添加一个新宠物",
}, func(ctx context.Context, input *struct {
Body Pet
}) (*struct{}, error) {
return nil, nil
})
huma.Register(api, huma.Operation{
OperationID: "get-pet",
Method: "GET",
Path: "/pet/{petId}",
Summary: "获取一个宠物",
}, func(ctx context.Context, input *PetID) (*struct {
Body Pet
}, error) {
return nil, nil
})
huma.Register(api, huma.Operation{
OperationID: "find-pet-by-status",
Method: "GET",
Path: "/pet/findByStatus",
Summary: "根据状态查找宠物",
}, func(ctx context.Context, input *struct {
Status string `path:"status" example:"available" doc:"用于过滤的状态" enum:"available,pending,sold"`
}) (*struct {
Body []Pet
}, error) {
return nil, nil
})
})
cli.Root().AddCommand(&cobra.Command{
Use: "openapi",
Run: func(cmd *cobra.Command, args []string) {
b, err := api.OpenAPI().MarshalJSON()
if err != nil {
panic(err)
}
fmt.Println(string(b))
},
})
cli.Run()
}