1、架构
transport(传输层):主要负责与HTTP、gRPC、thrift等相关逻辑,或使用NATS等发布订阅系统相
互通信,除此之外,还支持AMQP和thrift等多种网络通信模式;
endpoint(接口层):是服务器和客户端的基本构建块。在go-kit中服务中的每个对外提供的接口方
法都会定一个端点(endpoint),以便服务器和客户端之间进行网络通信。每个断电使用传输层通过
http或者grpc等通信模式对外提供服务。使用时定义request与response格式,并可以使用装饰器
包装函数,从而来实现各种中间件嵌套;
service(业务层):实现具体的业务逻辑,不会包含具体的网络传输或者消息类型的编解码,只存放
业务类接口

context

上下文 context.Context Go 语言中用来设置截止日期、同步信号,传递请求相关值的结构体。上下文与 Goroutine 有比较密切的关系,是 Go 语言中独特的设计,在其他编程语言中我们很少见到类似的概念。

context.Context 是 Go 语言在 1.7 版本中引入标准库的接口1,该接口定义了四个需要实现的方法,其中包括:

  • Deadline — 返回 context.Context 被取消的时间,也就是完成工作的截止日期;
  • Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消后关闭,多次调用 Done 方法会返回同一个 Channel;
  • Err — 返回 context.Context 结束的原因,它只会在 Done 方法对应的 Channel 关闭时返回非空的值;如果 context.Context 被取消,会返回 Canceled 错误;如果 context.Context 超时,会返回 DeadlineExceeded 错误;
  • Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数

type Context interface {    Deadline() (deadline time.Time, ok bool)    Done() <-chan struct{}    Err() error    Value(key interface{}) interface{} }


context 包中提供的 context.Background 、 context.TODO 、 context.WithDeadline 和context.WithValue 函数会返回实现该接口的私有结构体。

默认上下文

context 包中最常用的方法还是 context.Background 、 context.TODO ,这两个方法都会返回预先初始化好的私有变量 background 和 todo,它们会在同一个 Go 程序中被复用:


func Background() Context {
       return background
  }
  func TODO() Context {
       return todo
  }


从源代码来看, context.Background 和 context.TODO 也只是互为别名,没有太大的差别,只是在使用和语义上稍有不同:

  • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生出来;
  • context.TODO 应该仅在不确定应该使用哪种上下文时使用;

在多数情况下,如果当前函数没有上下文作为入参,我们都会使用 context.Background 作为起始的上下文向下传递。

接口

隐式接口

很多面向对象语言都有接口这一概念,例如 Java 和 C#。Java 的接口不仅可以定义方法签名,还可以定义变量,这些定义的变量可以直接在实现接口的类中使用,这里简单介绍一下 Java 中的接口:


public interface MyInterface {
         public String hello = "Hello";
         public void sayHello();
  }


上述代码定义了一个必须实现的方法 sayHello 和一个会注入到实现类的变量 hello。在下面的代码中,MyInterfaceImpl 实现了 MyInterface 接口:


public class MyInterfaceImpl implements MyInterface {
        public void sayHello() {
        System.out.println(MyInterface.hello);
  }
  }


Java 中的类必须通过上述方式显式地声明实现的接口,但是在 Go 语言中实现接口就不需要使用类似的方式。首先,我们简单了解一下在 Go 语言中如何定义接口。定义接口需要使用 interface 关键字,在接口中我们只能定义方法签名,不能包含成员变量,一个常见的 Go 语言接口是这样的:


type error interface {
     Error() string
  }


如果一个类型需要实现 error 接口,那么它只需要实现 Error() string 方法,下面的 RPCError 结构体就是error 接口的一个实现:


type RPCError struct {
  Code int64
  Message string
  }
  func (e *RPCError) Error() string {
  return fmt.Sprintf("%s, code=%d", e.Message, e.Code)
  }


上述代码根本就没有 error 接口的影子,这是为什么呢?Go 语言中接口的实现都是隐式的,我们只需要实现 Error() string 方法就实现了 error 接口。Go 语言实现接口的方式与 Java 完全不同:在 Java 中:实现接口需要显式地声明接口并实现所有方法;在 Go 中:实现接口的所有方法就隐式地实现了接口;我们使用上述 RPCError 结构体时并不关心它实现了哪些接口,Go 语言只会在传递参数、返回参数以及变量赋值时才会对某个类型是否实现接口进行检查,这里举几个例子来演示发生接口类型检查的时机:


func main() {
      var rpcErr error = NewRPCError(400, "unknown err") // typecheck1
      err := AsErr(rpcErr) // typecheck2
      println(err)
  }
  
  func NewRPCError(code int64, msg string) error {
       return &RPCError{ // typecheck3
       Code: code,
       Message: msg,
       }
  }
  
  func AsErr(err error) error {
       return err
  }


将 *RPCError 类型的变量赋值给 error 类型的变量 rpcErr;将 *RPCError 类型的变量 rpcErr 传递给签名中参数类型为 error 的 AsErr 函数;将 *RPCError 类型的变量从函数签名的返回值类型为 error 的 NewRPCError 函数中返回;

路由组件mux

mux代表“ HTTP请求多路复用器”。像standard一样 http.ServeMux , mux.Router 将传入的请求与已注册的路由列表进行匹配,并为与URL或其他条件匹配的路由调用处理程序。主要特点是:

  • 它实现了 http.Handler 接口,因此与标准兼容 http.ServeMux 。
  • 可以基于URL主机,路径,路径前缀,方案,标头和查询值,HTTP方法或使用自定义匹配器来匹配请求。
  • URL主机,路径和查询值可以具有带有可选正则表达式的变量。
  • 可以构建或“反转”已注册的URL,这有助于维护对资源的引用。
  • 路由可用作子路由:仅在父路由匹配时才测试嵌套路由。这对于定义具有共同条件(例如主机,路径前缀或其他重复属性)的路由组很有用。另外,这可以优化请求匹配

使用

func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
  // 解析路由变量
      vars := mux.Vars(r)
      w.WriteHeader(http.StatusOK)
      fmt.Fprintf(w, "Category: %v\n", vars["category"])
  
  }
  func main() {
      r := mux.NewRouter()
  // 绝对路径
      r.HandleFunc("/", HomeHandler)
      r.HandleFunc("/products", ProductsHandler)
      r.HandleFunc("/articles", ArticlesHandler)
  // 带参数
       r.HandleFunc("/products/{key}", ProductHandler)
       r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
       r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
       http.Handle("/", r)
  }


mux: gorilla/mux 实现了一个请求路由和分发的 Go 框架

Transport

定义http handler

使用mux路由转发组件 mux.NewRouter()


func MakeHttpHandler(ctx context.Context, endpoints endpoint.ChannelEndpoints, logger log.Logger) http.Handler {
      r := mux.NewRouter()
      options := []kithttp.ServerOption{
          kithttp.ServerErrorHandler(transport.NewLogErrorHandler(logger)),
          kithttp.ServerErrorEncoder(encodeError),
      }

      r.Methods("GET").Path("/health").Handler(kithttp.NewServer(
          endpoints.HealthCheckEndpoint,
          decodeHealthCheckRequest,
          encodeJsonResponse,
          options...,
      ))
 
      return r
  }


encode & decode


func decodeHealthCheckRequest(ctx context.Context, r *http.Request) (interface{}, error) {
      return model.HealthRequest{}, nil
   }
   
   func encodeError(_ context.Context, err error, w http.ResponseWriter) {
      w.Header().Set("Content-Type", "application/json; charset=utf-8")
      switch err {
      default:
          w.WriteHeader(http.StatusInternalServerError)
      }
      json.NewEncoder(w).Encode(map[string]interface{}{
          "error": err.Error(),
      })
   }


Endpoint

定义业务endpoint

gRPC

Viper读取配置文件

网关 gateway