Go 框架三件套详解(Web/RPC/ORM) | 青训营笔记
这是我参与「第五届青训营 」笔记创作活动的第5天
概述
本次课程讲解Go的框架三件套:Gorm、Kitex和Hertz的上手和实战应用分析
GORM:一款优秀的Golang ORM类库
Kitex:字节开源的高性能、强拓展的Golang微服务RPC框架
Hertz:字节开源的Golang微服务http框架
目录
框架
Gorm
安装
go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite
Gorm的默认约定
-
使用ID字段作为主键
-
在未使用TableName()函数定义表名的前提下,会使用结构体的蛇形复数作为默认表名
-
同上,在未定义go tag:
gorm :"column: xxx"的前提下,会使用字段名的蛇形作为列名 -
使用CreateAt、UpdateAt作为创建、更新时间(在软删除中有用)
-
首先定义
基本使用方法
定义Gorm Module

设定默认值
为Module定义表名
- 创建TableName函数,如果不定义则自动使用结构体蛇形复数作为表名
Gorm连接数据库
- Gorm通过驱动来连接数据库,这意味着在连接前要导入对应数据库的驱动

DSN:数据源名称。格式
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
插入数据
- 注意会插入完成后会对设定的对象回填属性值,如上图中的主键ID
当出现数据冲突时,使用clause.onConflict,配置冲突方案:比如说DoNothing:True

查询数据
- 使用Find()函数,当查找不到数据时会返回ErrRecordNotFound的错误,但当使用Find()查找多条数据时,查找不到数据时会返回错误

- 条件查询语句使用Where()函数包含查询条件,并在查询之前运行
- 其中,Where函数还可以使用结构体作为查询条件,但是注意,Gorm只会查询结构体的非零字段,这意味着如果要使用零值构建查询,需要使用Map来构建查询条件
更新数据
- 使用Update()可以更新记录,更新前需要使用db.Module(&ModuleInstance{xx, xx})或者TableName()来获取表名
- 如果使用结构体来更新字段,只会更新其中的非零字段
- 如果本身为零值,需要使用map来构建查询
- 在获取表名后,可以使用Select()函数来指定字段更新
- Update的参数表中传入gorm.Expr(),可更新SQL表达式

删除数据
删除分两大方面:物理删除、软删除
- 物理删除
- Delete()需要传入待删除的结构体,用于确定修改的表,第二个参数用于确定删除的条件
- Delete()需要传入待删除的结构体,用于确定修改的表,第二个参数用于确定删除的条件
- 软删除
- Gorm原生提供了软删除的能力,无需业务实现
- 软删除结构定义
- 软删除结构在删除后,不会真的在数据库中删除记录,而是使用Update更新了DeleteAt字段为删除时间。软删除后,再次查询时会忽略软删除的记录,但是调用链中使用Unscoped()函数可以重新查询到
事务
Gorm提供了Begin、Commit、Rollback方法用于事务的执行。
- 注意,开启事务在底层是固化了连接池的对象!!!因此开启事务后不可返回给db对象,否则会拖垮整个系统的运行!!!

自动事务
- 使用Transaction()函数,将业务逻辑函数作为参数传入该函数的参数表即可完成自动事务,避免多条件时漏写rollback和commit。
Gorm Hook
- GORM在提供了CURD的Hook能力。Hook是在创建、查询、更新、删除等操作之前、之后自动调用的函数。 如果任何Hook返回错误,GORM将停止后续的操作并回滚事务(开启默认事务时)。
Gorm性能优化
- 一开始建立连接时,对Open()函数的第二个参数传入gorm.Config{}的指针,当指定配置参数给该指针对象时,就可修改全局的特性,从而实现特定场景下的性能优化(关闭默认事务或者预编译语句)。
Kitex
安装
-
前者安装Kitex,后者安装thriftgo(thrift编译器的go语言实现)
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest go install github.com/cloudwego/thriftgo@latest
简单kitex上手
创建项目目录
mkdir kexample && cd kexample
定义IDL
-
RPC协议的实现需要知道对方的接口是什么样的,需要什么参数以及返回值的结构如何。这些需要使用IDL(Interface description language)来约定双方的协议。
-
使用thrift语法构建的简单样例
namespace go api struct Request { 1: string message } struct Response { 1: string message } service Echo { Response echo(1: Request req) } -
使用kitex生成服务端代码
kitex -module kexample -service kexample echo.thrift
- -module 后跟生成项目的模块名
- -service 后跟生成服务端项目名
完成代码生成后,项目目录

生成项目代码后,使用go mod tidy整理依赖
完成服务端逻辑
-
服务端逻辑代码在handler.go中,kitex已经根据idl生成了服务函数Echo,需要我们填充逻辑

-
补充完逻辑
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) { return &api.Response{Message: req.Message}, nil }
运行服务端
注意一个问题!!!自动导入的thrift版本过高导致直接运行下面的构建命令会报错

解决方法
在项目mod中修改thrift版本,后重新使用
go mod tidy整理依赖,再执行下面的构建运行指令
- 先构建服务输出
sh build.sh - 再运行服务
sh output/bootstrap.sh
编写客户端
- 新建客户端文件夹,编写客户端main.go
mkdir client && cd client && touch main.go
-
编写client/main.go
package main import ( "context" "github.com/cloudwego/kitex/client/callopt" "kexample/kitex_gen/api/echo" "log" "time" ) import "github.com/cloudwego/kitex/client" import "kexample/kitex_gen/api" func main() { c, err := echo.NewClient("kexample", client.WithHostPorts("0.0.0.0:8888")) if err != nil { log.Fatal(err) } req := &api.Request{Message: "my request"} resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second)) if err != nil { log.Fatal(err) } log.Println(resp) }echo.NewClient用于创建client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数, 此处的client.WithHostPorts用于指定服务端的地址上述代码中,我们首先创建了一个请求
req, 然后通过c.Echo发起了调用。其第一个参数为
context.Context,通过通常用其传递信息或者控制本次调用的一些行为,你可以在后续章节中找到如何使用它。其第二个参数为本次调用的请求。
其第三个参数为本次调用的
options,Kitex 提供了一种callopt机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的callopt.WithRPCTimeout用于指定此次调用的超时
运行客户端
go run client/main.go
Hertz
安装
go install github.com/cloudwego/hertz/cmd/hz@latest
简单hertz上手
创建项目文件夹

使用hz new生成代码
编写main.go
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main() {
h := server.Default()
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"message": "pong"})
})
h.Spin()
}
拉取和整理依赖
go mod tidy
构建和运行demo
go build -o demo && ./demo

测试接口
curl http://127.0.0.1:8888/ping
响应成功
hertz路由
-
- 支持路由分组

- 路由优先级

绑定验证
在结构体定义中使用go tag标注信息,在Hertz中即可实现绑定和验证

hertz中间件
中间件对上下文对象使用Next方法,完成中间件调用链
引用参考
GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
biz-demo/easy_note at main · cloudwego/biz-demo (github.com)
Go 框架三件套详解.pptx - 飞书云文档 (feishu.cn)