简单使用 Goa 搭建微服务环境

发布于 2024-10-29 07:38:19 字数 8695 浏览 7 评论 0

首先目录文件一定要创建在 $GOPATH/src 下,假设做一个 cellar 服务,首先创建目录 $GOPATH/src/cellar , 然后在此目录下创建子文件夹 design 和文件 design/design.go

示例代码

source code list

在文件 design/design.go 中写入代码:

package design

import (
        . "github.com/raphael/goa/design"
        . "github.com/raphael/goa/design/dsl"
)

var _ = API("cellar", func() {
        Title("The virtual wine cellar")
        Description("A basic example of an API implemented with goa")
        Scheme("http")
        Host("localhost:8080")
})

var _ = Resource("bottle", func() {
        BasePath("/bottles")
        DefaultMedia(BottleMedia)
        Action("show", func() {
                Description("Retrieve bottle with given id")
                Routing(GET("/:bottleID"))
                Params(func() {
                        Param("bottleID", Integer, "Bottle ID")
                })
                Response(OK)
                Response(NotFound)
        })
})

var BottleMedia = MediaType("application/vnd.goa.example.bottle+json", func() {
        Description("A bottle of wine")
        Attributes(func() {
                Attribute("id", Integer, "Unique bottle ID")
                Attribute("href", String, "API href for making requests on the bottle")
                Attribute("name", String, "Name of wine")
        })
        View("default", func() {
                Attribute("id")
                Attribute("href")
                Attribute("name")
        })
})

代码解读

  • design 包名不限制,什么名字都可。
  • API 函数,用于声明 API,第一个参数为 API 名字,第二个参数是匿名函数,用于定义该 API 的基本属性。
  • Resource 用于声明服务资源(resource),因为 Goa 是 RESTful 设计理念, sourceREST 中囊括了 API 所能访问的所有属性,参数即地址,地址亦参数,http 的 GET、POST 等也属于资源范畴。因此函数 Resource 是 Goa 设计对象的主体。在上面的代码示例中,函数 Resource 用于声明一个名为 “bottle” 的资源,同样的,在匿名函数中对该资源进行属性、行为声明。
  • Resource 函数中, Action 可以定义该资源所提供的行为, Action 函数必须在 Resource 函数中定义。 Action 同样是一个行为名称+一个匿名函数,在匿名函数中声明了该行为的描述、HTTP method、参数及返回值等属性。其中, HTTP method (代码中的 Routing 声明)中的路径需使用通配符( :变量名 )。承接参数类型定义使用 Params ,如果该 Action 不止一个参数,需要多次调用 Params 对每个参数进行承接声明。同一个 Resource 函数中,可以定义多个 Action 函数。
  • Goa 用代码描述设计,用设计生成代码。 Goa 设计的最终端点是 Action ,并通过函数 Action 描述。Goa 定义了基本的 response 模板,这里默认返回 OK(200)。
  • 当资源访问成功后,可以将访问变量提交给我们最后定义的 Media ,Media 通过函数 MediaType 定义,Media 的结构组成,则是匿名函数 Attributes 中定义。

生成代码

通过 goagen 将设计生成代码:

goagen bootstrap -d cellar/design

代码目录结构

在当前工作目录下会生成如下文件结构:

app
app/contexts.go
app/controllers.go
app/hrefs.go
app/media_types.go
app/user_types.go
main.go
bottle.go
client
client/cellar-cli
client/cellar-cli/main.go
client/cellar-cli/commands.go
client/client.go
client/bottle.go
swagger
swagger/swagger.json
swagger/swagger.go

bottle.go 为主结构代码文件。当之后再做设计修改,运行 goagen 工具后,该文件不会改变,只改变 appclientswagger 下的文件。通过 goagen 命令行参数可以指定 app 名定向生成代码。

目录功能说明

  • app 目录中的代码是 API 的胶水代码(脚手架代码),用于实现 HTTP 底层功能,包括信息接收管理,信息反馈模板,底层路由分发,以及 goa 定义的媒介结构体。如果没有特殊要求,这个目录下的代码文件系自动生成,不需要认为修改。
  • client 包含一个客户端的简单实现,支持命令行参数,可以进行简单的 server request 测试。
  • swagger 目录中存放 swagger 规范的 API 说明文件(swagger.json),通过访问 swagger 可以查看 API 说明。
  • 主目录 src 下的 main.gobottle.go 实现简单的 server。

app 包:

  • controllers.go :控制接口。在设计过程中,每一个 resource 都有一个接口定义。例子中的接口定义如下:
// BottleController is the controller interface for the Bottle actions.
type BottleController interface {
	goa.Controller
	Show(*ShowBottleContext) error
}

因此接口需要一个 goa.Controller ,需要实现 Show(*ShowBottleContext) error 函数,后者在 bottle.go 中实现。

controllers.go 还实现了向 server mount 的功能,将其自身 API 挂载到 server 中。

  • contexts.go :内容结构体的实现。
  • hrefs.go :提供资源信息描述功能。
  • media_types.go :实现应答数据结构体类型的定义。
  • user_types.go :定义通过 "Type" 设计的结构体。

完善代码

仍旧是上面代码的例子。Goa 已经实现了一个基本且功能全面的框架,剩下的需要我们实现 bottle 控制器。回到之前介绍的 BottleController 接口(定义在 controllers.go 文件中)和 ShowBottleContext 结构体(定义在 app/contexts.go 文件中):

// BottleController is the controller interface for the Bottle actions.
type BottleController interface {
	goa.Controller
	Show(*ShowBottleContext) error
}
// ShowBottleContext provides the bottle show action context.
type ShowBottleContext struct {
	*goa.Context
	BottleID int
}

其中 BottleID int 是我们在之前 design 时设定的。

次文件包含另外两个函数的实现:

// NotFound sends a HTTP response with status code 404.
func (ctx *ShowBottleContext) NotFound() error {
	return ctx.RespondBytes(404, nil)
}

// OK sends a HTTP response with status code 200.
func (ctx *ShowBottleContext) OK(resp *GoaExampleBottle) error {
	r, err := resp.Dump()
	if err != nil {
		return fmt.Errorf("invalid response: %s", err)
	}
	ctx.Header().Set("Content-Type", "application/vnd.goa.example.bottle+json; charset=utf-8")
	return ctx.Respond(200, r)
}

上面两个函数仍是 Template,供后续开发或 bottle.go 文件调用。

利用上面的基础框架,我们需要实现文件 bottle.go 的具体功能。当前的 API 访问时仅返回一个 200,我们需要根据具体业务来实现 API 功能函数。

用以下代码替换 show 方法(注意引用 fmt 包):

// Show implements the "show" action of the "bottles" controller.
func (c *BottleController) Show(ctx *app.ShowBottleContext) error {
	if ctx.BottleID == 0 {
		// Emulate a missing record with ID 0
		return ctx.NotFound()
	}
	// Build the resource using the generated data structure
	bottleID := fmt.Sprintf("Bottle #%d", ctx.BottleID)
	bottleHref := app.BottleHref(ctx.BottleID)
	bottle := app.GoaExampleBottle{
		ID:   &ctx.BottleID,
		Name: &bottleID,
		Href: &bottleHref,
	}

	// Let the generated code produce the HTTP response using the
	// media type described in the design (BottleMedia).
	return ctx.OK(&bottle)
}

上面代码与官方示例代码略有不同,但本质相同。官方代码有赋值错误,将 value 赋值给了 point,因此我做了过程上的微小处理。

API 实现后,会在 main.go 中进行挂载。

注意 :main 中进行挂载的 API 不能有路由冲突,比如 API 资源 /foo/:id/foo/:fooID/bar/:id 不能同时存在,需要将第一个修改为 /foo/:fooID

编译测试

在当前目录进行编译并启动 server:

go build -o cellar_server
./cellar_server

官方示例中是 go build -o cellar ,但生成过程中本身已存在 cellar 目录,所有会出现编译错误。这里改成 cellar_server

编译过程中出现其他问题,移步到我 上一篇关于 Goa 的日志

运行后,打印启动日志:

INFO[01-21|18:22:11] mount                                    app=API ctrl=Bottle action=Show route="GET /bottles/:bottleID"
INFO[01-21|18:22:11] mount                                    app=API file=swagger/swagger.json route="GET /swagger.json"
INFO[01-21|18:22:11] listen                                   app=API addr=:8080

从日志中看,路由中挂载了两个 API,分别为 Bottleswagger ,方法均为 GET

curl 工具测试

可以通过 curl 工具进行测试:

curl -i localhost:8080/bottles/1234
curl -i localhost:8080/bottles/0

第二个是测试之前我们实现的功能:

        if ctx.BottleID == 0 {
		// Emulate a missing record with ID 0
		return ctx.NotFound()
	}

swagger 测试:

curl -i localhost:8080/swagger.json

client CLI 测试

Goa 同时生成 CLI 测试客户端:

$ cd client/cellar-cli
$ go build -o cellar-cli
$ ./cellar-cli --dump show bottle /bottles/1234

效果与 curl 大体相同,多了一个 href 的显示:

{"href":"/bottles/1234","id":1234,"name":"Bottle #1234"}

通过这个例子,可以体会 Goa 以设计为基础的 HTTP API 开发。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

仲春光

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

玍銹的英雄夢

文章 0 评论 0

我不会写诗

文章 0 评论 0

十六岁半

文章 0 评论 0

浸婚纱

文章 0 评论 0

qq_kJ6XkX

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文