Go 框架三件套详解(Web/RPC/ORM) | 青训营笔记

296 阅读6分钟

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

image-20230120180154968

设定默认值

image-20230120182352402

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

DSN:数据源名称。格式[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

插入数据

image-20230120182546933

  • 注意会插入完成后会对设定的对象回填属性值,如上图中的主键ID 当出现数据冲突时,使用clause.onConflict,配置冲突方案:比如说DoNothing:True image-20230120183810478
查询数据
  • 使用Find()函数,当查找不到数据时会返回ErrRecordNotFound的错误,但当使用Find()查找多条数据时,查找不到数据时会返回错误

image-20230120185313340

image-20230120191623929

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

image-20230120194158554

删除数据

删除分两大方面:物理删除、软删除

  • 物理删除
    • Delete()需要传入待删除的结构体,用于确定修改的表,第二个参数用于确定删除的条件 image-20230120194612789
  • 软删除
    • Gorm原生提供了软删除的能力,无需业务实现
    • 软删除结构定义 image-20230120194747075
    • 软删除结构在删除后,不会真的在数据库中删除记录,而是使用Update更新了DeleteAt字段为删除时间。软删除后,再次查询时会忽略软删除的记录,但是调用链中使用Unscoped()函数可以重新查询到 image-20230120195050176
事务

Gorm提供了Begin、Commit、Rollback方法用于事务的执行。

  • 注意,开启事务在底层是固化了连接池的对象!!!因此开启事务后不可返回给db对象,否则会拖垮整个系统的运行!!! image-20230120195502036

自动事务

  • 使用Transaction()函数,将业务逻辑函数作为参数传入该函数的参数表即可完成自动事务,避免多条件时漏写rollback和commit。

image-20230120195913667

Gorm Hook
  • GORM在提供了CURD的Hook能力。Hook是在创建、查询、更新、删除等操作之前、之后自动调用的函数。 如果任何Hook返回错误,GORM将停止后续的操作并回滚事务(开启默认事务时)。
image-20230120200209727
Gorm性能优化
  • 一开始建立连接时,对Open()函数的第二个参数传入gorm.Config{}的指针,当指定配置参数给该指针对象时,就可修改全局的特性,从而实现特定场景下的性能优化(关闭默认事务或者预编译语句)。 image-20230120200543636

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)
    }
    
  • image-20230121115020083

使用kitex生成服务端代码

kitex -module kexample -service kexample echo.thrift

  • -module 后跟生成项目的模块名
  • -service 后跟生成服务端项目名

完成代码生成后,项目目录 image-20230121115425794

生成项目代码后,使用go mod tidy整理依赖

完成服务端逻辑
  • 服务端逻辑代码在handler.go中,kitex已经根据idl生成了服务函数Echo,需要我们填充逻辑 image-20230121115719133

  • 补充完逻辑

    func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
      return &api.Response{Message: req.Message}, nil
    }
    
运行服务端

注意一个问题!!!自动导入的thrift版本过高导致直接运行下面的构建命令会报错

image-20230121120115767

解决方法

在项目mod中修改thrift版本,后重新使用go mod tidy整理依赖,再执行下面的构建运行指令

image-20230121120311520
  • 先构建服务输出sh build.sh
  • 再运行服务sh output/bootstrap.sh

image-20230121125956851

编写客户端
  • 新建客户端文件夹,编写客户端main.go mkdir client && cd client && touch main.go
image-20230121130151311
  • 编写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

image-20230121134421591

Hertz

安装

go install github.com/cloudwego/hertz/cmd/hz@latest

简单hertz上手
创建项目文件夹

image-20230121134858749

使用hz new生成代码
image-20230121135127549
编写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

image-20230121135447325

测试接口

curl http://127.0.0.1:8888/ping

响应成功

image-20230121140449084

hertz路由
  • image-20230121140824027
  • 支持路由分组 image-20230121141011963
  • 路由优先级 image-20230121141053851
绑定验证

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

hertz中间件

中间件对上下文对象使用Next方法,完成中间件调用链

image-20230121141729151

引用参考


GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

Kitex | CloudWeGo

Hertz | CloudWeGo

biz-demo/easy_note at main · cloudwego/biz-demo (github.com)

‌⁢⁣⁡⁡‍‌⁡⁢⁡⁢‌⁡⁢‌⁤‌⁢‬‍⁡⁣⁡⁡‬‍⁤⁡‍‍⁢‌‍‍⁣‍Go 框架三件套详解.pptx - 飞书云文档 (feishu.cn)