初识Go-kit

33 阅读6分钟

1.架构

1、Service

这里就是我们的业务类、接口等相关信息存放

2、EndPoint

定义Request、Response格式,并可以使用装饰器(闭包)包装函数,以此来实现各个中间件嵌套

3、Transport

主要负责与HTTP、gRPC、thrift等相关逻辑

// 项目结构
-| Server
----| server.go
-| EndPoint
----| endpoint.go
-| Transport
----| Transport.go
- main.go

image.png

2.小案例--熟悉开发流程

(0) 小知识点补充

type TaskDo struct {
    TaskId string `gorm:"column:task_id"` //这个关键字是和orm数据库关联的,指的是TaskId对应的是表中的task_id列
    Body   string `gorm:"column:body"`
    Status int    `gorm:"column:status"`
    Ip     string `gorm:"ip"`
}

(1) 首先写Server层业务类

// Server/server.go
package Server

import "fmt"

// server.go 实现业务

// IServer 用于定义业务方法的接口
type IServer interface {
    // 这里只需要关注我 IServer 对业务所需要的方法即可
    // 例如: 我这里要实现一个问候的方法 和一个 bye的方法 比较简单 传入一个名字 返回一个名字
    Hello(name string) string
    Bye(name string) string
}

// Server 用于实现上面定义的接口
type Server struct {
    // 根据业务需求填充结构体...
}

// 实现上方定义的业务方法

func (s Server) Hello(name string) string {
    return fmt.Sprintf("%s:Hello", name)
}

func (s Server) Bye(name string) string {
    return fmt.Sprintf("%s:Bye", name)
}

(2) 再写EndPoint中的内容

Server业务中有几个方法,EndPoint中就应该有几个对应的方法

// EndPoint/endpoint.go
package EndPoint

import (
    "Songzhibin/go-kit-demo/v0/Server"
    "context"
    "github.com/go-kit/kit/endpoint"
)

// endpoint.go 定义 Request、Response 格式, 并且可以使用闭包来实现各种中间件的嵌套
// 这里了解 protobuf 的比较好理解点
// 就是声明 接收数据和响应数据的结构体 并通过构造函数创建 在创建的过程当然可以使用闭包来进行一些你想要的操作啦

// 这里根据我们Demo来创建一个响应和请求
// 当然你想怎么创建怎么创建 也可以共用 这里我分开写 便于大家看的清楚

// Hello 业务使用的请求和响应格式
// HelloRequest 请求格式
type HelloRequest struct {
    Name string `json:"name"`
}
/*
Name string:定义了一个名为 Name 的字符串类型字段。
`json:"name"`:这是一个结构体字段标签,用于指定该字段在 JSON 序列化和反序列化时的行为。
1.如果你有一个 HelloRequest 实例 req := HelloRequest{Name: "Alice"},序列化后的 JSON 字符串将是 {"name": "Alice"}。
2.如果你有一个 JSON 字符串 {"name": "Alice"},反序列化后会得到一个 HelloRequest 实例 req := HelloRequest{Name: "Alice"}。
3.它的初始化定义: r:=HelloRequest{Name:"Alice"}
*/
// HelloResponse 响应格式
type HelloResponse struct {
    Reply string `json:"reply"`
}

// Bye 业务使用的请求和响应格式
// ByeRequest 请求格式
type ByeRequest struct {
    Name string `json:"name"`
}

// ByeResponse 响应格式
type ByeResponse struct {
    Reply string `json:"reply"`
}

// ------------ 当然 也可以通用的写 ----------
// Request 请求格式
type Request struct {
    Name string `json:"name"`
}

// Response 响应格式
type Response struct {
    Reply string `json:"reply"`
}

// 这里创建构造函数 hello方法的业务处理
// MakeServerEndPointHello 创建关于业务的构造函数
// 传入 Server/server.go 定义的相关业务接口
// 返回 go-kit/endpoint.Endpoint (实际上就是一个函数签名)
func MakeServerEndPointHello(s Server.IServer) endpoint.Endpoint {//Endpoint.endpoint其实是一个func()类型的变量
    // 这里使用闭包,可以在这里做一些中间件业务的处理
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        // request 是在对应请求来时传入的参数(这里的request 实际上是等下我们要将Transport中一个decode函数中处理获得的参数)
        // 这里进行以下断言
        r, ok := request.(HelloRequest)//将request转为HelloRequest类型
        if !ok {
            return Response{}, nil
        }
        // 这里实际上就是调用我们在Server/server.go中定义的业务逻辑
        // 我们拿到了 Request.Name 那么我们就可以调用我们的业务 Server.IServer 中的方法来处理这个数据并返回
        // 具体的业务逻辑具体定义....
        return HelloResponse{Reply: s.Hello(r.Name)}, nil
        // response 这里返回的response 可以返回任意的 不过根据规范是要返回我们刚才定义好的返回对象

    }
}

// 这里创建构造函数 Bye方法的业务处理
// MakeServerEndPointBye 创建关于业务的构造函数
// 传入 Server/server.go 定义的相关业务接口
// 返回 go-kit/endpoint.Endpoint (实际上就是一个函数签名)
func MakeServerEndPointBye(s Server.IServer) endpoint.Endpoint {
    // 这里使用闭包,可以在这里做一些中间件业务的处理
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        // request 是在对应请求来时传入的参数(这里的request 实际上是等下我们要将的Transport中一个decode函数中处理获得的参数)
        // 这里进行以下断言
        r, ok := request.(ByeRequest)
        if !ok {
            return Response{}, nil
        }
        // 这里实际上就是调用我们在Server/server.go中定义的业务逻辑
        // 我们拿到了 Request.Name 那么我们就可以调用我们的业务 Server.IServer 中的方法来处理这个数据并返回
        // 具体的业务逻辑具体定义....
        return ByeResponse{Reply: s.Bye(r.Name)}, nil
        // response 这里返回的response 可以返回任意的 不过根据规范是要返回我们刚才定义好的返回对象
    }
}

(3) 最后是Transport--封装request后传给endpoint

// Transport/transport.go
package Transport

import (
    "Songzhibin/go-kit-demo/v0/EndPoint"
    "context"
    "encoding/json"
    "errors"
    "net/http"
)

// Transport/transport.go 主要负责HTTP、gRpc、thrift等相关的逻辑

// 这里有两个关键函数
// DecodeRequest & EncodeResponse 函数签名是固定的哟
// func DecodeRequest(c context.Context, request *http.Request) (interface{}, error)
// func EncodeResponse(c context.Context, w http.ResponseWriter, response interface{}) error

// HelloDecodeRequest 解码 后封装至 EndPoint中定义的 Request格式中
func HelloDecodeRequest(c context.Context, request *http.Request) (interface{}, error) {
    // 这里主要就是通过 request 拿到对应的参数构造成在 EndPoint中定义的 Request结构体即可

    name := request.URL.Query().Get("name")
    if name == "" {
        return nil, errors.New("无效参数")
    }
    // 这里返回的是
    return EndPoint.HelloRequest{Name: name}, nil
}

// HelloEncodeResponse 通过响应封装成 EndPoint中定义的 Response结构体即可
func HelloEncodeResponse(c context.Context, w http.ResponseWriter, response interface{}) error {
    // 这里将Response返回成有效的json格式给http

    // 设置请求头信息
    w.Header().Set("Content-Type", "application/json;charset=utf-8")
    // 使用内置json包转换
    return json.NewEncoder(w).Encode(response)
}

// ByeDecodeRequest 解码 后封装至 EndPoint中定义的 Request格式中
func ByeDecodeRequest(c context.Context, request *http.Request) (interface{}, error) {
    // 这里主要就是通过 request 拿到对应的参数构造成在 EndPoint中定义的 Request结构体即可

    name := request.URL.Query().Get("name")
    if name == "" {
        return nil, errors.New("无效参数")
    }
    // 这里返回的是
    return EndPoint.ByeRequest{Name: name}, nil
}

// sayEncodeResponse 通过响应封装成 EndPoint中定义的 Response结构体即可
func sayEncodeResponse(c context.Context, w http.ResponseWriter, response interface{}) error {
    // 这里将Response返回成有效的json格式给http

    // 设置请求头信息
    w.Header().Set("Content-Type", "application/json;charset=utf-8")
    // 使用内置json包转换
    return json.NewEncoder(w).Encode(response)
}

(4) 服务启动

// main.go
package main

import (
    EndPoint1 "Songzhibin/go-kit-demo/v0/EndPoint"
    "Songzhibin/go-kit-demo/v0/Server"
    "Songzhibin/go-kit-demo/v0/Transport"
    httpTransport "github.com/go-kit/kit/transport/http"
    "net/http"
)

// 服务发布

func main() {
    // 1.先创建我们最开始定义的Server/server.go
    s := Server.Server{}

    // 2.在用EndPoint/endpoint.go 创建业务服务
    hello := EndPoint1.MakeServerEndPointHello(s)
    Bye := EndPoint1.MakeServerEndPointBye(s)

    // 3.使用 kit 创建 handler
    // 固定格式
    // 传入 业务服务 以及 定义的 加密解密方法
    helloServer := httpTransport.NewServer(hello, Transport.HelloDecodeRequest, Transport.HelloEncodeResponse)
    sayServer := httpTransport.NewServer(Bye, Transport.ByeDecodeRequest, Transport.ByeEncodeResponse)

    // 使用http包启动服务
    go http.ListenAndServe("0.0.0.0:8000", helloServer)

    go http.ListenAndServe("0.0.0.0:8001", sayServer)
    select {}
}

补充:

var (
    once = sync.Once{}
    // 存储某个日期是否为工作日
    holidayMap = map[string]string{}
)
func NewTaskImpl(config *config2.DispatcherConfig) *TaskImpl {
    config.AppleQueryList = strings.Split(config.AppleQuery, ",")
    config.SxQueryList = strings.Split(config.SxQuery, ",")
    taskImpl := &TaskImpl{config: config}
    once.Do(func() {
        go taskImpl.TickerTask()
    })
    return taskImpl
}
sync.Once{} 它里面有一个Do方法,无论调用多少次,Do方法内部的业务方法只能执行一次。
因此,无论NewTaskImpl被调用多少次,里面的匿名函数只会调一次