热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go微服务框架gokratos实战02:proto代码生成和编码实现步骤

在上一篇kratosquickstart文章中,我们直接用kratosnew命令生成了一个项目。这一篇来看看kratosAPI的定义和使用。一、kratos中API简介1.1简介A

在上一篇 kratos quickstart 文章中,我们直接用 kratos new 命令生成了一个项目。

这一篇来看看 kratos API 的定义和使用。


一、kratos 中 API 简介


1.1 简介

API 全称是 Application Programming Interface,应用程序接口。

在 kratos 中,API 指的是 REST API 和 RPC API ,REST API 是用户访问应用程序时的入口,

RPC API 作为应用程序内部相互访问的接口定义。

那怎么定义 API?使用的是 protocol-buffers 这种与编程语言无关的接口自定义语言(IDL),它可以根据定义的 pb 来生成你

所需的编程语言程序。

gRPC 是 Go 语言编写的一个开源的 RPC 框架,它使用的 IDL 就是 protocol-buffers。

protocol-buffers 语法学习可以参考文档:



  • proto3 语法, https://developers.google.com/protocol-buffers/docs/proto3



    • 中译版 (时间有点早2017-03)



  • proto2 语法,https://developers.google.com/protocol-buffers/docs/proto




二、kratos 中 API 定义和使用

下面一步一步实现 api 文件(proto 文件)生成,然后根据 proto 文件生成对应的 pb.http, pb.grpc 代码。

然后生成 service 代码,使用 service 代码。然后编写 biz 代码等等步骤。

来理清 kratos 里代码编写的步骤。毕竟 internal 文件夹里各种 go 文件业务逻辑顺序还是要有些繁琐。


2.1 快速生成 proto 文件

在上一篇文章的项目基础上生成一个新的 API(proto 文件)。

先安装 kratos cli :

go install github.com/go-kratos/kratos/cmd/kratos/v2@latest

kratos cli 工具使用文档:https://go-kratos.dev/docs/getting-started/usage

进入项目 quickstart 目录,运行命令:

kratos proto add api/helloworld/v1/student.proto

在 api/helloworld/v1 目录先就会出现一个 student.proto 的文件,

image-20220531122225550

里面的代码:

syntax = "proto3";
package api.helloworld.v1;
option go_package = "quickstart/api/helloworld/v1;v1";
option java_multiple_files = true;
option java_package = "api.helloworld.v1";
service Student {
rpc CreateStudent (CreateStudentRequest) returns (CreateStudentReply);
rpc UpdateStudent (UpdateStudentRequest) returns (UpdateStudentReply);
rpc DeleteStudent (DeleteStudentRequest) returns (DeleteStudentReply);
rpc GetStudent (GetStudentRequest) returns (GetStudentReply);
rpc ListStudent (ListStudentRequest) returns (ListStudentReply);
}
message CreateStudentRequest {}
message CreateStudentReply {}
message UpdateStudentRequest {}
message UpdateStudentReply {}
message DeleteStudentRequest {}
message DeleteStudentReply {}
message GetStudentRequest {}
message GetStudentReply {}
message ListStudentRequest {}
message ListStudentReply {}

生成了一个 student.proto 的模板,定义了一些基本操作,Create、Update、Delete、Get、List。


2.2 给 proto 添加内容

学习 greeter.proto 里的用法,给 student.proto 添加一个简单的 HTTP 转换。

添加一个 hello 的 http 转换接口

第一步:引入 import "google/api/annotations.proto";

第二步:在 service Student 里添加代码:

在服务里定义一个 Hello 的操作,然后在里面用 option (google.api.http) 语法,如下:

rpc Hello (HelloReq) returns (HelloResp) {
option (google.api.http) = {
get: "/hello/{name}"
};
}

定义 HelloReq 和 HelloResp:

请求的字段和返回的字段

message HelloReq {
string name = 1;
}
message HelloResp {
string message = 1;
}

上面就是把 HTTP REST 转换为 gRPC :















HTTPgRPC
GET /hello/tomHello(name: "tom")

还可以给这个接口添加额外的接口,用 additional_bindings

rpc Hello (HelloReq) returns (HelloResp) {
option (google.api.http) = {
// 定义 GET 接口,把 name 参数映射到 HelloReq
get: "/hello/{name}",
// 添加额外的接口
additional_bindings {
// 定义了一个 POST 接口,并且把 body 映射到了 HelloReq
post: "/hello/{id}/sayhello/{sayname}",
body: "*",
}
};
}
// 这里的 HelloReq 和 HelloResp
message HelloReq {
string name = 1;
string id = 2;
string sayname = 3;
}
message HelloResp {
string message = 1;
string text = 2;
}

HTTP 转换问 gRPC:



















HTTPgRPC
GET /hello/tomHello(name: "tom")
POST /hello/123/sayhello/tom {text: "world!"}Hello(id: "123", sayname:"tom" text:"world!")

2.3 生成 proto 对应代码

通过 make 命令生成:

make api

或者通过 kratos cli 生成:

kratos proto client api/helloworld/v1/student.proto

这里通过 kratos proto client api/helloworld/v1/student.proto 来生成 proto 对应的代码:


api/helloworld/v1/student.pb.go

api/helloworld/v1/student_grpc.pb.go

// 注意 http 代码只会在 proto 文件中声明了 http 时才会生成

api/helloworld/v1/student_http.pb.go


image-20220531183850578


2.4 生成 Service 代码

通过 proto 文件,直接生成对应的 Service 代码。使用 -t 指定生成目录:

kratos proto server api/helloworld/v1/student.proto -t internal/service

image-20220531192634236

internal/service/student.go:

package service
import (
"context"
pb "quickstart/api/helloworld/v1"
)
type StudentService struct {
pb.UnimplementedStudentServer
}
func NewStudentService() *StudentService {
return &StudentService{}
}
func (s *StudentService) Createstudent(ctx context.Context, req *pb.CreateStudentRequest) (*pb.CreateStudentReply, error) {
return &pb.CreateStudentReply{}, nil
}
func (s *StudentService) Updatestudent(ctx context.Context, req *pb.UpdateStudentRequest) (*pb.UpdateStudentReply, error) {
return &pb.UpdateStudentReply{}, nil
}
func (s *StudentService) Deletestudent(ctx context.Context, req *pb.DeleteStudentRequest) (*pb.DeleteStudentReply, error) {
return &pb.DeleteStudentReply{}, nil
}
func (s *StudentService) Getstudent(ctx context.Context, req *pb.GetStudentRequest) (*pb.GetStudentReply, error) {
return &pb.GetStudentReply{}, nil
}
func (s *StudentService) Liststudent(ctx context.Context, req *pb.ListStudentRequest) (*pb.ListStudentReply, error) {
return &pb.ListStudentReply{}, nil
}
func (s *StudentService) Hello(ctx context.Context, req *pb.HelloReq) (*pb.HelloResp, error) {
return &pb.HelloResp{}, nil
}

看上面的代码,里面的内容是空的,需要你自己编写相应的代码逻辑。

通过上一篇文章我们知道,service 实现了 api 定义的服务,其实就是 student.proto 里定义的服务。它要把数据传输对象(比如 http request data) 传入到 internal/biz 里进行处理,它一般不会涉及业务逻辑代码。业务逻辑的组装会在 biz 里实现。

有了 service/student.go ,怎么使用?


2.5 向 wire 中注入 Service 代码

在 kratos 中,组织代码是用 wire 依赖注入的方式。

在 internal/service/service.go 文件里加上 NewStudentService:

var ProviderSet = wire.NewSet(NewGreeterService, NewStudentService)

假如我们要通过 http 来访问,那又要怎么做?对,还需要在服务端加 student 服务代码。


2.6 向server添加代码

向 internal/server/http.go,internal/server/grpc.go 添加服务代码:

在 http.go 中:

// 在函数参数中添加 student *service.StudentService
func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, student *service.StudentService, logger log.Logger) *http.Server {
... ...

srv := http.NewServer(opts...)
v1.RegisterGreeterHTTPServer(srv, greeter)
v1.RegisterStudentHTTPServer(srv, student) // 在 httpserver 上注册 student
return srv
}

在 grpc.go 中:

// 在函数参数中添加 student *service.StudentService
func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, student *service.StudentService, logger log.Logger) *grpc.Server {
... ...

v1.RegisterGreeterServer(srv, greeter)
v1.RegisterStudentServer(srv, student) // 在 grpcserver 上注册 student
return srv
}

那需不需要在向 wire 注册后才能使用呢?不需要,在 internal/server/server.go 中已经有了:

var ProviderSet = wire.NewSet(NewHTTPServer, NewGRPCServer)

接下来,接受了参数,是不是要对参数进行相应处理。


顺序是:service -> biz -> data



2.7 业务逻辑 biz

先简单分析下 internal/biz/greeter.go 里的代码。

// 定义了一个 Greeter struct,主要内容就是定义 Greeter 的字段
type Greeter struct {
Hello string
}
// 对 Greeter 定义操作接口 GreeterRepo
type GreeterRepo interface {
Save(context.Context, *Greeter) (*Greeter, error)
Update(context.Context, *Greeter) (*Greeter, error)
FindByID(context.Context, int64) (*Greeter, error)
ListByHello(context.Context, string) ([]*Greeter, error)
ListAll(context.Context) ([]*Greeter, error)
}
// 操作加上日志
type GreeterUsecase struct {
repo GreeterRepo
log *log.Helper
}
// 初始化 GreeterUsercase
func NewGreeterUsecase(repo GreeterRepo, logger log.Logger) *GreeterUsecase
// 对 Greeter 的真正操作,用到的方法都是上面 GreeterRepo 定义的
func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {
uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
return uc.repo.Save(ctx, g)
}

基本步骤:1.定义 struct,里面包含字段 2.定义操作 struct 的 interface 3.给操作加上日志 4.定义真正执行操作函数


这里只定义了操作的接口 GreeterRepo interface,里面定义了常规的操作。

而操作接口里定义的操作需要到 data 里实现。


照葫芦画瓢,在 internal/biz/ 文件夹下新建文件 student.go:

1.定义 struct Student:

type Student struct {
ID string
Name string
Sayname string
}

2.定义对 struct student 的操作接口:

type StudentRepo interface {
Save(context.Context, *Student) (*Student, error)
Get(context.Context, *Student) (*Student, error)
}

3.对 student 的操作加上日志:

type StudentUsercase struct {
repo StudentRepo
log *log.Helper
}

4.初始化 StudentUsercase

func NewStudentUsercase(repo StudentRepo, logger log.Logger) *StudentUsercase {
return &StudentUsercase{repo: repo, log: log.NewHelper(logger)}
}

5.编写 CreateStudent 方法,也就是一些业务逻辑编写

func (uc *StudentUsercase) CreateStudent(ctx context.Context, stu *Student) (*Student, error) {
uc.log.WithContext(ctx).Infof("CreateStudent: %v", stu.ID)
return uc.repo.Save(ctx, stu)
}

biz 里就是完成业务逻辑组装,数据的处理。

6.向 wire 注入 student

internal/biz/biz.go:

var ProviderSet = wire.NewSet(NewGreeterUsecase, NewStudentUsercase)

上面对 struct student 定义了操作的接口,那具体实现在哪里实现?就是在 internal/data 里实现。


2.8 持久化操作

可以仿照 2.7 小结,先看看 internal/data/greeter.go 怎么编写代码的。

greeter.go 里的具体代码就留给读者自己研究了。

下面开始编写 internal/data/student.go 代码。

1.定义持久化的 struct

type studentRepo struct {
data *Data // 这里 *Data 是连接数据库客户端
log *log.Helper
}

2.初始化 studentRepo struct

func NewStudentRepo(data *Data, logger log.Logger) biz.StudentRepo {
return &studentRepo{
data: data,
log: log.NewHelper(logger),
}
}

3.实现接口定义的操作

在 biz/student.go 里的 StudentRepo 接口,定义了 2 个操作 Save、Get,在这里实现,

func (repo *studentRepo) Save(ctx context.Context, stu *biz.Student) (*biz.Student, error) {
return stu, nil
}
func (repo *studentRepo) Get(ctx context.Context, stu *biz.Student) (*biz.Student, error) {
return stu, nil
}

上面是一个实现的模板代码。


2.9 配置文件

配置文件是放在 internal/conf 文件夹中,这里放置了配置文件结构的定义文件,使用 .proto 进行配置定义,

然后通过在根目录执行 make config 命令,就可以将对应的 .pb.go 文件生成到同一目录下使用。

在初始状态下,这个 conf.proto 所定义的结构,就是 configs/config.yaml 的接口,请保持两者一致。


每次修改配置文件后,记得使用 make config 命令重新生成 go 文件。



2.10 重新生成 wire_gen.go 文件

进入到 cmd/quickstart 目录,然后直接用 wire 命令重新生成 wire_gen.go 文件。

// cmd/quickstart
wire

wire 的用法可以看这篇文章:Go 依赖注入工具 wire 使用


这篇文章已经写的有点长了,接下来的一篇文章结合 gorm 进行一些简单的增加修改列表等简单的操作。


三、参考



  • https://go-kratos.dev/docs/getting-started/usage kratos cli 工具使用

  • https://go-kratos.dev/docs/component/api kratos api 定义

  • https://cloud.google.com/endpoints/docs/grpc/transcoding http/json 转码为 gRPC

  • https://go-kratos.dev/docs/guide/api-protobuf/ Protobuf 规范

  • https://go-kratos.dev/docs/component/config 配置

  • https://developers.google.com/protocol-buffers/docs/proto3 proto3 文档

  • https://colobu.com/2017/03/16/Protobuf3-language-guide/ Protobuf3 语法指南(中译)



推荐阅读
  • 微服务之总体架构篇
    一、单体架构存在的问题缺点:1、难以维护:当单体应用业务不断迭代后代码量非常臃肿,模整个项目非常复杂,每次更改代码都可能带来新的bug;2、部署项目麻烦:庞大之后项目部署效率 ... [详细]
  • Istio是一个用来连接、管理和保护微服务的开放平台。Istio提供一种简单的方式来为已部署的服务建 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • [翻译]微服务设计模式5. 服务发现服务端服务发现
    服务之间需要互相调用,在单体架构中,服务之间的互相调用直接通过编程语言层面的方法调用就搞定了。在传统的分布式应用的部署中,服务地 ... [详细]
  • 服务网关与流量网关
    一、为什么需要服务网关1、什么是服务网关传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关& ... [详细]
  • zuul 路由不生效_Zuul网关到底有何牛逼之处?竟然这么多人在用~
    作者:kosamino来源:cnblogs.comjing99p11696192.html哈喽,各位新来的小伙伴们,大家好& ... [详细]
  • 后台自动化测试与持续部署实践
    后台自动化测试与持续部署实践https:mp.weixin.qq.comslqwGUCKZM0AvEw_xh-7BDA后台自动化测试与持续部署实践原创 腾讯程序员 腾讯技术工程 2 ... [详细]
  • 都说Python处理速度慢,为何月活7亿的 Instagram依然在使用Python?
    点击“Python编程与实战”,选择“置顶公众号”第一时间获取Python技术干货!来自|简书作者|我爱学python链接|https:www.jian ... [详细]
  • SOA架构理解理解SOA架构,了解ESB概念,明白SOA与微服务的区别和联系,了解SOA与热门技术的结合与应用。1、面向服务的架构SOASOA(ServiceOrien ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
author-avatar
漫步乡间2012
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有