Skip to content

设计优先呢?#

引言#

设计优先是一种 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()
}