1. 用go-kit整合http服务

基本思想解析

1、核心的分层概念

我们使用这个框架,首要的就是要了解service、endpoint、transport这三个概念并编写对应代码,然后可以拼成一个服务供访问。

2、我们先来聊聊transport层怎么来的?

我们要搭建的微服务体系,不确定用户会用使用什么协议,比如http、grpc、thrift等等,这些得交给一个层去实现,这个就是transport层。

它可以对多种协议进行处理的实现,但是他与endpoint层之间就用规范的接口标准去定义,这样层与层之间解耦合并有清晰的接口关系。

transport底层负责把协议中数据提取出来,交给endpoint层处理,然后拿到endpoint层返回的数据,再通过协议封装返回。 总结下来,transport层就是要确认用什么协议,然后对每个路由应用一个endpoint层的接口实现。

不同协议返回的数据不一样,http的和rpc的数据有自己的格式,不过给到endpoint和从endpoint拿的数据都是一样的, 这样transport层还得实现对每个路由有关的一对DecodeRequest和EncodeResponse函数。

总结下来transport层就是两件事,一是对接具体协议,二是编解码数据对接enpoint层。

3、再来聊聊endpoint层怎么来的?

上面说到接口,这个接口如果是我们怎么设计呢,我们需要处理过来的路由,一个路由一个处理器。

处理器就是一个函数,参数是从协议里解析的,不确定格式我们就用万能的interface{},返回值也不好说,那就也用万能的interface{},这样一来, 我们可以定义出这样一个接口就叫Point吧,Point接口就一个方法如上所说的,就是func(context.Context, interface{}) (interface{}, error)。 transport层处理完协议把不同路由的请求就调用不同的Point接口实现即可。

考虑去实现Point接口,对一个路由我们创立一个对应结构体实现Point接口,里面的方法就是具体业务实现。这样代码写的很繁琐,既然接口就一个方法, 那不如直接定义一个函数类型得了,这样就出来了EndPoint函数类型,对一个路由我们实现一个函数类型对象即可。如下就是Endpoint函数类型:

type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)

4、再来聊聊service层怎么来的?

其实上面说到的两层即endpoint和transport就已经可以把代码规划层次然后各司其职了, 把所有业务逻辑都写到一个个enpoint这个函数类型的实现中。

但在实际开发中,我们得用很多中间件,得用redis、用mysql、用其他依赖服务等等,对应的项目中就是一个个struct并构建之间复杂的关系。

类似的我们可能会构建出一套MVC分层的代码来实现业务。对一个具体的请求,我们使用Controller层与之对接。基于这个背景,go-kit让我们实现一个业务层, 这里就叫service层。在service层实现业务处理的接口,endpoint也不关心你的具体实现,就是定义好接口方法,它处理的时候按照标准和service层交互就好了。

5、最后看看怎么把这三层整合起来呢?

首先endpoint层依赖service层,我们定义一个接口比如就叫IService,我们实例化endpoint的时候就提供一个具体的IService接口实现。

上面我们说到Endpoint实现不是一个标准struct,如果是我们可以给他加上IService依赖,实例化的时候提供实现即可。现在我们的每个EndPoint已经 是一个函数类型实现,想要往里填入对IService接口的依赖,可以考虑使用闭包。闭包可以让函数类型的实现依赖外部的变量,而且在使用时在加载。 因此,我们本来要实现一个个Endpoint函数类型的实现,现在要改为实现一个个闭包,它让一个函数类型对象和外部变量绑定了。

func MakeEndPointArticleAdd(svc my_service.IService) endpoint.Endpoint {
   return func(ctx context.Context, request interface{}) (response interface{}, err error) {
      req := request.(my_service.ArticleAddRequest)
      res := svc.ArticleAdd(ctx, req)
      return res, nil
   }
}

在说下transport层和endpoint层,endpoint本身就是一种函数类型了,我们实现一个transport层的处理器时,就可以提供一个Endpoint函数类型实现。 比如对于http协议的处理,transport要提供一个Server结构体,它包含了Endpoint、DecodeRequest、EncodeResponse, 我们实例化它时提供好我们的endpoint层的具体实现接口。

handler := httpTransport.NewServer(
   endpointArticleAdd,
   my_tansport.DecodeArticleAddRequest,
   my_tansport.EncodeArticleAddResponse,
)

一个demo来说明

在项目use_gokit/v1/app中,展示了如何基于go-kit架子搭建出一个http服务。

1.  总体设计

在下面的main.go中就能看到整体的结构。

  1. 先创建定义的业务处理接口实现,service
  2. 在创建业务服务的函数签名,endpoint
  3. 使用kit创建handler
  4. http路由和服务
package main

import (
	"fmt"
	httpTransport "github.com/go-kit/kit/transport/http"
	"net/http"
	"swwgo/use_gokit/v1/app/my_endpoint"
	"swwgo/use_gokit/v1/app/my_service"
	"swwgo/use_gokit/v1/app/my_tansport"
)

func main() {
	// 1.先创建定义的业务处理接口实现, service
	svc := my_service.NewService()

	// 2.再创建业务服务的函数签名,endpoint
	endpointArticleAdd := my_endpoint.MakeEndPointArticleAdd(svc)

	// 3.使用kit创建handler
	handler := httpTransport.NewServer(
		endpointArticleAdd,
		my_tansport.DecodeArticleAddRequest,
		my_tansport.EncodeArticleAddResponse,
	)

	// 4. http路由和服务
	m := http.NewServeMux()
	m.Handle("/article", handler)
	fmt.Println("server run 0.0.0.0:8888")
	err := http.ListenAndServe("0.0.0.0:8888", m)
	if err != nil {
		println(err.Error())
	}
	select {

	}
}

2. service

各层之间要用接口定义进行组合,所以这里先定义出一个service层的接口,如下面的“IService”

我们定义出一个业务处理,如下面的ArticleAdd,它的入参ArticleAddRequest,出参ArticleAddResponse。

然后实现这个接口的结构体,如下面的baseService。

my_service/service.go

package my_service

import (
	"context"
)

// IService 用于定义业务方法的接口
type IService interface {
	ArticleAdd(ctx context.Context, param ArticleAddRequest) ArticleAddResponse
}

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

func (b baseService) ArticleAdd(ctx context.Context, param ArticleAddRequest) ArticleAddResponse {
	return ArticleAddResponse{
		Data: param.Name + param.Content,
	}
}

func NewService() IService {
	return &baseService{}
}

my_service/request.go

package my_service

type ArticleAddRequest struct {
	Name    string `json:"name"`
	Content string `json:"content"`
}

my_service/response.go

package my_service

type ArticleAddResponse struct {
	Data string `json:"data"`
}

3. endpoint

为了对接一个接口服务,构建一个endpoint,它依赖于service接口。

my_endpoint/endpoint.go

package my_endpoint

import (
	"context"
	"github.com/go-kit/kit/endpoint"
	"swwgo/use_gokit/v1/app/my_service"
)

// MakeEndPointArticleAdd 创建关于业务的构造函数
// 传入service层定义的相关业务接口
// 返回 endpoint.Endpoint, 实际就是一个函数签名
func MakeEndPointArticleAdd(svc my_service.IService) endpoint.Endpoint {
	// 这里使用闭包,可以在这里做一些业务的处理
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		// request是对应请求过来时传入的参数,实际上是Transport中一个decode函数处理得到的
		// 需要进行下断言
		req := request.(my_service.ArticleAddRequest)

		// 这里就是调用service层定义的业务逻辑
		// 把拿到的数据作为参数
		res := svc.ArticleAdd(ctx, req)

		// 返回值可以是任意的,不过根据规范要返回我们刚才定义好的返回对象
		return res, nil
	}
}

4. transport

 Transport 负责HTTP、gRPC、thrift等相关协议的请求逻辑。

对每一个请求都要实现一对参数解码和返回值编码的函数签名。

my_transport/transport.go

package my_tansport

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"swwgo/use_gokit/v1/app/my_service"
)

// Transport 负责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

// DecodeRequest解码,请求参数封装为Endpoint中定义的Request格式
func DecodeArticleAddRequest(_ context.Context, r *http.Request) (interface{}, error) {
	var param my_service.ArticleAddRequest
	param.Name = r.FormValue("name")
	param.Content = r.FormValue("content")

	if param.Name == "" {
		return nil, errors.New("name不能为空")
	}

	return param, nil
}

// EncodeResponse编码,把业务的响应封装成想要的结构
func EncodeArticleAddResponse(ctx 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)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值