茶艺师学微服务(准备篇3)

129 阅读4分钟

茶艺师学微服务(准备篇3)

前言

在准备篇2中,我们生成好了 proto 文件对应的 go 代码文件与 grpc 代码文件。
但是要怎么使用呢?
有这么一句话,就是“如何学会了一个东西?那就先写出它的测试样例”。
因此,在这我们就试着写一下 grpc 的测试样例。

思路

我们说到底,就是要实现服务端与客户端的通信。

只不过这次用的是 go 的 grpc 而已。
因此,待会我们的测试,还是得做以下事情:

  • 实现服务端
  • 模拟服务端启动
  • 模拟客户端启动,并模拟发送请求
  • 查看从服务端返回的信息是否符合预期

这里,我们就模拟这么一个场景:客户端存储着两个人的信息,id 为123的 Bob 和 id 为 456 的 Alice ,然后从客户端带着 id 来访问, id 对了会返回各自的名字, id 错了返回“没有这用户”

实现服务端

实现服务端,这里就按照简单的来。

还记得在proto文件里所定义的 service 吗,这里就把里面的 GetById 给定义出来就好。
在这之前,先设定好用谁来当 server 。
既然是使用 grpc ,那么就用 grpc 生成出来的 UnimplementedUserSvcServer (起不同的名字,生成的名字自然会不同,要在 _grpc.pd.go 文件里找)。

就是注释为这个的

接着按照设想的逻辑,把 GetById 给定义好出来,大致是这样

type Server struct {
	UnimplementedUserSvcServer
}

var _ UserSvcServer = &Server{}

func (s *Server) GetById(ctx context.Context, request *GetByIdRep) (*GetByIdResp, error) {
	list := map[int64]*GetByIdResp {
		123: &GetByIdResp{User: &User{
			Id: 123,
			Name: "Bob",
		}},
		456: &GetByIdResp{User: &User{
			Id: 456,
			Name: "Alice",
		}},
	}

	_, ok := list[request.Id]
	if !ok {
		// 创建gRPC错误
		err := status.Errorf(codes.NotFound, "没有该用户")
		return nil, err
	}

	return list[request.Id], nil
}

其中需要注意的是, grpc 的错误信息,不能用简单的 errors.New() ,得用 grpc 所定义的错误,位于 google.golang.org/grpc/status 里的 status.Errorf()

模拟服务端启动

整个模拟过程大致有以下步骤:

  1. 创建一个新的gRPC服务器对象 server(同时用 defer 关键字设定好该对象的优雅停止)
  2. 创建一个 userServer 对象,该对象是自定义的服务器实现,用于处理用户服务。
  3. 使用 RegisterUserSvcServer 函数将 userServer 注册到 server 中,以便服务器能够处理来自客户端的请求。
  4. 使用 net.Listen 函数在本地的8090端口上监听 TCP 连接,返回一个 Listener 对象 l。(如果监听过程中发生错误,使用 panic 函数抛出异常,并终止程序的执行。)
  5. 使用 server.Serve(l) 启动服务器,开始监听来自客户端的请求,并将请求分发给相应的处理程序。(当服务器停止监听或发生错误时,server.Serve(l) 将返回一个错误对象 err。)
  6. 使用 t.Log(err) 将错误信息记录到测试日志中,以便在测试结果中进行查看。

就像这样:

func TestServer(t *testing.T) {
	server := grpc.NewServer()
	defer func() {
		server.GracefulStop()
	}()

	userServer := &Server{}
	RegisterUserSvcServer(server, userServer)
	l, err := net.Listen("tcp", ":8090")
	if err != nil {
		panic(err)
	}
	err = server.Serve(l)
	t.Log(err)
}

在 IDE 里运行该程序后,会是这样的:

这表示着服务端一直跑着,在等着客户端的请求。

模拟客户端启动,并模拟发送请求

先说模拟客服端启动,其过程是:

  1. 准备一个连接池
  2. 用连接池准备客户端
  3. 用客户端发起调用

由于 grpc 错误的特殊性,因此在客户端里,也要按照以下步骤处理 grpc 错误:

  1. 当存在错误时,先判断是不是 grpc 的 status.FromError() 错误类型(如果不是,就抛出错误)
  2. 再判断里面的错误码 st.Code() 是不是在服务端里定义的 codes.NotFound,是的话,读出里面的错误信息,如果不是,就抛出错误

代码示例:

			// 创建gRPC连接
			cc, err := grpc.Dial(":8090",
				grpc.WithTransportCredentials(insecure.NewCredentials()))
			require.NoError(t, err)
			client := NewUserSvcClient(cc)
			_, cancel := context.WithTimeout(context.Background(), time.Second * 30)
			defer cancel()

			resp, err := client.GetById(context.Background(), &GetByIdRep{Id: tc.reqId})
			if err != nil {
				// 检查错误类型
				st, ok := status.FromError(err)
				if !ok {
					t.Fatalf("Failed to get gRPC status: %v", err)
				}

				// 检查错误代码
				if st.Code() == codes.NotFound {
					t.Log(tc.name, "Error:", st.Message())
				} else {
					t.Fatalf("Unexpected error: %v", err)
				}
			} else {
				t.Log(tc.name, resp.User)
			}

然后把测试样例输进入就好。

查看从服务端返回的信息是否符合预期

实际测试结果符合预期,还行。

结语

通过编写测试样例,试了一下 grpc 的大致用法,为我们的微服务化做准备。
而测试本身,就是微服务化之前的最大准备。
这我们下次再说。