go 教程:go kit 微服务实战 | go优质外文翻译 | go 技术论坛-江南app体育官方入口
什么是 go kit?
来自 readme.md:
go kit 是用于在 go 中构建微服务的编程工具包。我们解决了分布式系统和应用程序体系结构中的常见问题,因此您可以专注于交付业务价值。
[...]
go是一种很棒的通用语言,但是微服务需要一定量的专业支持。 rpc安全性,系统可观察性,基础结构集成甚至程序设计。go kit 填补了标准库留下的空白,并使 go 成为在任何组织中编写微服务的一流语言。
我们会做什么?
我们将创建一个非常基本的微服务,该微服务将返回并验证日期。目标是了解 go kit 的工作原理,仅此而已。不用 go kit 就能轻松复制所有逻辑,但我们是来这里学习 go kit 的,所以...
希望这能对你的下一个项目有一个良好的开端!
我们的微服务将有一些端点。
- 在
/status
处的一个get
端点,该端点将返回一个简单的回复,以确认微服务已启动并正在运行 - 在
/get
处的一个get
端点,它将返回今天的日期 - 在
/validate
处的一个post
端点,它将收到一个dd/mm/yyyy
的日期字符串 (美国的日期格式) 并根据一个简单的正则表达式对其进行验证…
源代码:
先决条件
您应该已经在计算机上安装并运行了 。我发现在我的 macbook 上 (我在配置环境变量时遇到一些问题) 安装的 golang 比 运行得更好。
另外,你应该了解 go 语言。例如,我不会解释什么是 struct
。
napodate 微服务
好的,让我们在我们的 $gopath
文件夹中创建一个名为 napodate
的新文件夹。它也将会是我们的包名。
将 service.go
文件放在此处。让我们在文件顶部添加服务接口。
package napodate
import "context"
// 服务为您的应用程序提供了一些「日期功能」
type service interface {
status(ctx context.context) (string, error)
get(ctx context.context) (string, error)
validate(ctx context.context, date string) (bool, error)
}
在这里,我们为服务定义了「蓝图」:在 go kit 中,您必须将服务建模为接口。如上所述,我们将需要三个端点,这些端点将被映射到此接口。
为什么我们要使用 context
包? 请阅读 :
在 google,我们开发了一个上下文包,可轻松将跨 api 边界的请求范围的值,取消信号和截止日期传递给处理请求的所有协程
基本上,这是必需的,因为我们的微服务应该从一开始就处理并发请求,并且必须为每个请求提供上下文。
不要把事情搞混。本教程后面的内容将对此进行详细介绍。我们现在用的不多,但要习惯它! :p 现在我们有了微服务接口。
实现江南app体育官方入口的服务
正如你知道的,没有实现,接口就什么都不是,所以让我们实现江南app体育官方入口的服务吧。让我们为 service.go
添加更多代码。
type dateservice struct{}
// newservice 创建一个新的服务
func newservice() service {
return dateservice{}
}
// status 仅仅告诉我们江南app体育官方入口的服务是正常的!
func (dateservice) status(ctx context.context) (string, error) {
return "ok", nil
}
// get 将会返回今天的日期
func (dateservice) get(ctx context.context) (string, error) {
now := time.now()
return now.format("02/01/2006"), nil
}
// validate 将会检查日期是否为今天的日期
func (dateservice) validate(ctx context.context, date string) (bool, error) {
_, err := time.parse("02/01/2006", date)
if err != nil {
return false, err
}
return true, nil
}
新定义的类型 dateservice
(一个空结构) 是我们为了将服务的方法组合在一起,同时以某种方式向其他地方「隐藏」实现的方式。
请参阅 newservice()
作为我们「对象」的构造函数。这就是我们要获得服务实例的调用,同时要像优秀程序员一样隐藏内部的逻辑。
让我们写一个测试
在江南app体育官方入口的服务测试中可以看到一个关于如何使用 newservice()
的好例子。继续并创建一个 service_test.go
文件。
package napodate
import (
"context"
"testing"
"time"
)
func teststatus(t *testing.t) {
srv, ctx := setup()
s, err := srv.status(ctx)
if err != nil {
t.errorf("error: %s", err)
}
// 测试 status
ok := s == "ok"
if !ok {
t.errorf("expected service to be ok")
}
}
func testget(t *testing.t) {
srv, ctx := setup()
d, err := srv.get(ctx)
if err != nil {
t.errorf("error: %s", err)
}
time := time.now()
today := time.format("02/01/2006")
// 测试今天的日期
ok := today == d
if !ok {
t.errorf("expected dates to be equal")
}
}
func testvalidate(t *testing.t) {
srv, ctx := setup()
b, err := srv.validate(ctx, "31/12/2019")
if err != nil {
t.errorf("error: %s", err)
}
// 测试日期是否有效
if !b {
t.errorf("date should be valid")
}
// 测试无效日期
b, err = srv.validate(ctx, "31/31/2019")
if b {
t.errorf("date should be invalid")
}
// 测试美国日期
b, err = srv.validate(ctx, "12/31/2019")
if b {
t.errorf("usa date should be invalid")
}
}
func setup() (srv service, ctx context.context) {
return newservice(), context.background()
}
使测试更具可读性,但实际上应该使用 来编写它们。
测试是绿色的(!),但重点关注一下 setup()
方法。对于每个测试,我们使用 newservice()
和上下文返回一个服务实例。
传输协议
江南app体育官方入口的服务将使用 http 公开。我们将对接受的 http 请求和响应进行建模。在 service.go
的同一文件夹中创建一个名为 transport.go
的文件。
package napodate
import (
"context"
"encoding/json"
"net/http"
)
// 在文件的第一部分中,我们将请求和响应映射到它们的 json 有效负载。
type getrequest struct{}
type getresponse struct {
date string `json:"date"`
err string `json:"err,omitempty"`
}
type validaterequest struct {
date string `json:"date"`
}
type validateresponse struct {
valid bool `json:"valid"`
err string `json:"err,omitempty"`
}
type statusrequest struct{}
type statusresponse struct {
status string `json:"status"`
}
// 在第二部分中,我们将为传入的请求编写「解码器」
func decodegetrequest(ctx context.context, r *http.request) (interface{}, error) {
var req getrequest
return req, nil
}
func decodevalidaterequest(ctx context.context, r *http.request) (interface{}, error) {
var req validaterequest
err := json.newdecoder(r.body).decode(&req)
if err != nil {
return nil, err
}
return req, nil
}
func decodestatusrequest(ctx context.context, r *http.request) (interface{}, error) {
var req statusrequest
return req, nil
}
// 最后,我们有响应输出的编码器
func encoderesponse(ctx context.context, w http.responsewriter, response interface{}) error {
return json.newencoder(w).encode(response)
}
源代码:
如果你想要问我一些代码相关的问题,你可以在 repo 文件的 transport.go
中找到注释,这将有助于你找到它。
在文件的第一部分中,我们将请求和响应映射到它们的 json 有效负载。对于 statusrequest
和 getrequest
我们不需要太多,因为没有有效负载被发送到服务器。对于 validaterequest
我们将传递一个要验证的日期,因此这里是 date
字段。
响应也非常简单。
在第二部分 我们将为传入的请求编写「解码器」,告诉服务如何翻译请求并将它们映射到正确的请求结构。我知道 get
和 status
是空的,但它们是为了完整性而存在的。记住,我们在实践中学习...
最后但并非最不重要, 我们有用于响应输出的编码器,它是一个简单的 json 编码器:给定一个对象,我们将从中返回一个 json 对象。
以上就是传输协议,现在让我们去创建端点!
端点
让我们创建 endpoint.go
文件。该文件包含我们的端点,这些端点会将来自客户端的请求映射到我们的内部服务
package napodate
import (
"context"
"errors"
"github.com/go-kit/kit/endpoint"
)
// 公开端点
type endpoints struct {
getendpoint endpoint.endpoint
statusendpoint endpoint.endpoint
validateendpoint endpoint.endpoint
}
// makegetendpoint 返回 「get」服务的响应
func makegetendpoint(srv service) endpoint.endpoint {
return func(ctx context.context, request interface{}) (interface{}, error) {
_ = request.(getrequest) // 我们只需要请求,不需要使用它的值
d, err := srv.get(ctx)
if err != nil {
return getresponse{d, err.error()}, nil
}
return getresponse{d, ""}, nil
}
}
// makestatusendpoint 返回 「status」服务的响应
func makestatusendpoint(srv service) endpoint.endpoint {
return func(ctx context.context, request interface{}) (interface{}, error) {
_ = request.(statusrequest) // 我们只需要请求,不需要使用它的值
s, err := srv.status(ctx)
if err != nil {
return statusresponse{s}, err
}
return statusresponse{s}, nil
}
}
// makevalidateendpoint 返回「validate」服务的响应
func makevalidateendpoint(srv service) endpoint.endpoint {
return func(ctx context.context, request interface{}) (interface{}, error) {
req := request.(validaterequest)
b, err := srv.validate(ctx, req.date)
if err != nil {
return validateresponse{b, err.error()}, nil
}
return validateresponse{b, ""}, nil
}
}
// get 端点映射
func (e endpoints) get(ctx context.context) (string, error) {
req := getrequest{}
resp, err := e.getendpoint(ctx, req)
if err != nil {
return "", err
}
getresp := resp.(getresponse)
if getresp.err != "" {
return "", errors.new(getresp.err)
}
return getresp.date, nil
}
// status 端点映射
func (e endpoints) status(ctx context.context) (string, error) {
req := statusrequest{}
resp, err := e.statusendpoint(ctx, req)
if err != nil {
return "", err
}
statusresp := resp.(statusresponse)
return statusresp.status, nil
}
// validate 端点映射
func (e endpoints) validate(ctx context.context, date string) (bool, error) {
req := validaterequest{date: date}
resp, err := e.validateendpoint(ctx, req)
if err != nil {
return false, err
}
validateresp := resp.(validateresponse)
if validateresp.err != "" {
return false, errors.new(validateresp.err)
}
return validateresp.valid, nil
}
让我们深入探讨...为了将我们所有的服务方法 get()
,status()
和 validate()
作为端点公开, 我们将要编写处理传入请求的函数,调用相应的服务方法,并根据响应构建并返回适当的对象。
这些方法都是 make...
。它们接收 service 作为参数,然后使用类型断言将请求类型「强制」为特定类型,并使用它来为其调用服务方法。
在这些 make...
方法(将在 main.go
文件中使用)之后,我们将编写符合服务接口的端点。
type endpoints struct {
getendpoint endpoint.endpoint
statusendpoint endpoint.endpoint
validateendpoint endpoint.endpoint
}
让我们来看一个例子:
// status 端点映射
func (e endpoints) status(ctx context.context) (string, error) {
req := statusrequest{}
resp, err := e.statusendpoint(ctx, req)
if err != nil {
return "", err
}
statusresp := resp.(statusresponse)
return statusresp.status, nil
}
这个方法允许我们以 go 的方法来使用端点。
http 服务器
我们的微服务需要一个 http 服务器。go 对此很有帮助, 但是我选择了 作为我们的路由,因为它的语法看起来非常简洁,所以让我们创建一个映射到我们端点的 http 服务器。
在你的项目中创建 server.go
文件。
package napodate
import (
"context"
"net/http"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/gorilla/mux"
)
// newhttpserver 是一个很好的服务器
func newhttpserver(ctx context.context, endpoints endpoints) http.handler {
r := mux.newrouter()
r.use(commonmiddleware) // @请参阅 https://stackoverflow.com/a/51456342
r.methods("get").path("/status").handler(httptransport.newserver(
endpoints.statusendpoint,
decodestatusrequest,
encoderesponse,
))
r.methods("get").path("/get").handler(httptransport.newserver(
endpoints.getendpoint,
decodegetrequest,
encoderesponse,
))
r.methods("post").path("/validate").handler(httptransport.newserver(
endpoints.validateendpoint,
decodevalidaterequest,
encoderesponse,
))
return r
}
func commonmiddleware(next http.handler) http.handler {
return http.handlerfunc(func(w http.responsewriter, r *http.request) {
w.header().add("content-type", "application/json")
next.servehttp(w, r)
})
}
端点将从 main.go
文件和 commonmiddleware()
传递到服务器,请注意为未公开的端点的每个响应添加特殊的头信息。
最后,我们的 main.go 文件
让我们来完成我们的微服务吧!现在我们有一个带有端点的服务和一个 http 服务器。我们只需要一个可以打包它们的地方,这就是我们的 main.go
文件。将它放入名为 cmd
的文件夹中。
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"napodate"
)
func main() {
var (
httpaddr = flag.string("http", ":8080", "http listen address")
)
flag.parse()
ctx := context.background()
// our napodate service
srv := napodate.newservice()
errchan := make(chan error)
go func() {
c := make(chan os.signal, 1)
signal.notify(c, syscall.sigint, syscall.sigterm)
errchan <- fmt.errorf("%s", <-c)
}()
// 映射端点
endpoints := napodate.endpoints{
getendpoint: napodate.makegetendpoint(srv),
statusendpoint: napodate.makestatusendpoint(srv),
validateendpoint: napodate.makevalidateendpoint(srv),
}
// http 传输
go func() {
log.println("napodate is listening on port:", *httpaddr)
handler := napodate.newhttpserver(ctx, endpoints)
errchan <- http.listenandserve(*httpaddr, handler)
}()
log.fatalln(<-errchan)
}
让我们一起分析一下这个文件。我们声明 main
包并导入我们需要的内容。
, 我们服务的默认端口是经典的 8080
,但是我们要感谢标志让我们在任何端口上都可以使用江南app体育官方入口的服务。
下面是我们服务器的设置:我们创建一个 context(context的说明见上面)),然后我们得到江南app体育官方入口的服务。 .
通道(channels)是连接并发 goroutine 的管道。您可以将值从一个 goroutine发送到通道,并使用另一个 goroutine 接收。
我们然后创建两个 goroutines . 一个用于在我们按 ctrl c
时停止服务器,另一个将实际侦听传入请求。
看一下 handler := napodate.newhttpserver(ctx, endpoints)
此处理程序将映射我们的 endpoints (还记得上面使用 make...
方法不?)并返回正确的方法回答。
您以前在哪里看到过 newhttpserver()
?
一旦通道收到错误消息,服务器将停止并死机。
让我们启动服务!
如果你正确的执行了前文的所有操作,请运行下面的命令
go run cmd/main.go
在你的项目文件夹目录下,你应该可以 curl 你的微服务了!
curl http://localhost:8080/get
{"date":"14/04/2019"}
curl http://localhost:8080/status
{"status":"ok"}
curl -xpost -d '{"date":"32/12/2020"}' http://localhost:8080/validate
{"valid":false,"err":"parsing time \"32/12/2020\": day out of range"}
curl -xpost -d '{"date":"12/12/2021"}' http://localhost:8080/validate
{"valid":true}
结束
我们使用 go kit 从零开始创建了一个简单的微服务。
希望你和我一样喜欢本教程!
源代码:
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系江南app体育官方入口。
原文地址: