API

概念

Nirvana Definition

在 Nirvana 中,所有的 API 都是通过 Descriptor 和 Definition 进行描述的。首先看一个 List Messages 的 API 定义:

// 在使用 nirvana init 创建的标准项目结构中,这个文件位于 pkg/apis/v1/descriptors/message.go

func init() {
    register([]def.Descriptor{{
        // Path 定义了 API 路径
        Path:        "/messages",
        // Definitions 数组包含了这个路径下的所有定义。
        Definitions: []def.Definition{listMessages},
    },
    }...)
}

// listMessages 定义了一个返回 Message 列表的 API
var listMessages = def.Definition{
    // 这个 API 返回的是资源数组,所以使用 List 方法。
    Method:      def.List,
    // Summary 是一个短语,用于描述这个 API 的用途。这个短语在生成文档和客户端的时候用于区分 API。
    // 这个字符串去掉空格后会作为生成客户端时的函数名,因此请确保这个字符串是有意义的。
    Summary:     "List Messages",
    // 详细描述这个 API 的用途。
    Description: "Query a specified number of messages and returns an array",
    // 业务函数
    Function:    message.ListMessages,
    // 对应业务函数的参数信息。用于告知 Nirvana 从请求的那一部分取得数据,然后传递给业务函数。
    Parameters: []def.Parameter{
        {
            // 参数来源
            Source:      def.Query,
            // 参数名称,作为 key 从 Source 里取值。
            // 与业务函数的参数名称无关。
            Name:        "count",
            // 默认值
            Default:     10,
            // 参数描述 
            Description: "Number of messages",
        },
    },
    // 对应业务函数的返回结果。用于告知 Nirvana 业务函数返回结果如何放到请求的响应中。
    Results: def.DataErrorResults("A list of messages"),
}

根据上面的 API 定义,再对应业务函数:

// 在使用 nirvana init 创建的标准项目结构中,这个文件位于 pkg/message/message.go

// Message describes a message entry.
type Message struct {
    ID      int    `json:"id"`
    Title   string `json:"title"`
    Content string `json:"content"`
}

// ListMessages returns all messages.
func ListMessages(ctx context.Context, count int) ([]Message, error) {
    messages := make([]Message, count)
    for i := 0; i < count; i++ {
        messages[i].ID = i
        messages[i].Title = fmt.Sprintf("Example %d", i)
        messages[i].Content = fmt.Sprintf("Content of example %d", i)
    }
    return messages, nil
}

可以看到,业务函数既不关心参数的来源和类型转换,也不关心如何将返回值写到响应里,只是按照业务需求实现逻辑。

Definition Method

在 Nirvana 中,我们建议所有的 API 都遵守 RESTful 风格,并且在 URL 中携带 API 的版本号。下表中展示了 Nirvana 中定义的动作以及对应的 API 定义。

Nirvana 方法 HTTP 方法 HTTP 成功状态码 URL 描述
List GET 200 /apis/v1/resources 获取资源列表
Get GET 200 /apis/v1/resources/{resource} 根据资源唯一 ID/Name 获取资源
Create POST 201 /apis/v1/resources 创建一个资源(非幂等)
Update PUT 200 /apis/v1/resources/{resource} 更新一个资源(幂等)
Patch PATCH 200 /apis/v1/resources/{resource} 修改一个资源的部分内容
Delete DELETE 204 /apis/v1/resources/{resource} 删除一个资源
AsyncCreate POST 202 /apis/v1/resources 异步创建资源
AsyncUpdate PUT 202 /apis/v1/resources/{resource} 异步更新资源
AsyncPatch PATCH 202 /apis/v1/resources/{resource} 异步修改资源部分内容
AsyncDelete DELETE 202 /apis/v1/resources/{resource} 异步删除资源

对于 Nirvana 异步方法,发出后服务端应当只是将请求加入执行队列,然后立刻返回一个关联的对象或者链接,供客户端后续查询请求执行状态。

所有的 Nirvana 方法都是语义层面的,为的是提高 API 定义的可读性。也就是说 List 和 Get 在一个 HTTP 请求中使用的都是 GET,两者没有区别。 但是为了使 API 定义更加明确,我们应该根据场景确定使用哪个 Nirvana 方法。比如某个 API 是返回一个资源列表的,那么 Nirvana 方法就应该是 List 而不是 Get。

Definition Source

Definition Source 用于描述一个业务函数的参数的来源和默认值。

参数来源 名称 描述
Path 有 参数值来源于 API Path
Query 有 参数值来源于 URL Query
Header 有 参数值来源于 Request Header
Form 有 参数值来源于 Request Body,但是 Content-Type 必须是 application/x-www-form-urlencoded 或 multipart/form-data
File 有 参数值来源于 Request Body,但是 Content-Type 必须是 multipart/form-data
Body 无 参数值来源于 Request Body
Auto 无 Auto 类型对应的参数必须是一个结构体,通过结构体的 tag 定义来确定每个字段的来源
Prefab 有 参数值来源于当前 server 内部,比如一个 DB 链接

Auto 类型的 tag 范例如下:

type Example struct {
    Start       int    `source:"Query,start,default=100"`
    ContentType string `source:"Header,Content-Type"`
}

tag 名称为 source。值使用逗号分隔,第一个参数表示参数来源,第二个表示名称。如果是 Body 类型名称可以为空。 如果需要给字段设置默认值,则需要使用 default={value} 的形式。

如果有多个 Auto 结构体,可以组合成一个:

type AnotherAutoStruct struct {
    ...
}

type Example struct {
    Start       int    `source:"Query,start,default=100"`
    ContentType string `source:"Header,Content-Type"`
    AnotherAutoStruct
}

对于没有 source 的结构体类型,会递归遍历以寻找带有 source 的字段。忽略所有没有 source 的字段。

Definition Destination

Definition Destination 用于描述一个业务函数的参数的来源和默认值。

返回值目标 描述
Meta 这个返回值类型必须是 map[string]string,会写入 Response Header
Data 返回值可以是任意结构,自动转换并写入到 Response Body
Error 错误类型,必须是 error

给项目添加一个 API

接下来我们给项目增加一个 API,用于获取一条消息:

func init() {
    register([]def.Descriptor{{
        Path:        "/messages",
        Definitions: []def.Definition{listMessages},
    }, {
        // 获取一条消息的 Descriptor。
        Path:        "/messages/{message}",
        Definitions: []def.Definition{getMessage},
    },
    }...)
}

// 获取一条消息的 API 定义。
var getMessage = def.Definition{
    // 因为只获取一条消息,此处为 Get。
    Method:      def.Get,
    Summary:     "Get Message",
    Description: "Get a message by id",
    // 业务函数
    Function:    message.GetMessage,
    Parameters: []def.Parameter{
        // 这是一个工具方法,用于快速生成一个参数结构。
        // message 是从 API Path 里获取的。
        def.PathParameterFor("message", "Message id"),
    },
    Results: def.DataErrorResults("A message"),
}

对应的业务函数如下:

// GetMessage return a message by id.
func GetMessage(ctx context.Context, id int) (*Message, error) {
    return &Message{
        ID:      id,
        Title:   "This is an example",
        Content: "Example content",
    }, nil
}

添加 API 之后,编译运行。然后访问 http://localhost:8080/apis/v1/messages/100,即可获得结果(默认情况下都是 json 类型):

{"id":100,"title":"This is an example","content":"Example content"}

这里添加的业务函数都是以 Golang 函数的方式呈现的。如果希望使用实例方法作为业务的处理函数,请参考 方法包。

results matching ""

    No results matching ""