1.kratos 简介
kratos并不绑定于特定的基础设施,不限定于某种注册中心,或数据库orm等,所以您可以十分轻松地将任意库集成进项目里,与kratos共同运作。

api -> service(wire) -> db
- 可以看到kratos将整个服务大体分为了3层,api / service / db。
- 左侧标注了在 service和db层,使用依赖注入(di)进行实现,工具名称为wire。
- 可以看到wire这个工具几乎贯穿kratos架构始终,是一个大角色。
2.传输协议
支持http + grpc两种调用方式,通过编写proto文件来实现。
一般,http开放给外部调用,可以使用restful风格定义。grpc面向内部微服务之间进行调用。

在项目中,会以这样的结构出现,并且可以对不同协议进来的请求进行处理,添加处理的中间件,如权限校验、熔断限流等等。
3.日志
在kratos中,可以自定义日志框架选型,设置日志格式和输出内容,然后将logger对象以依赖注入的方式,分配给server中的grpc server和http server,这样就可以实现每次收到请求后的日志打印。
将logger对象以依赖注入的方式,注入到业务层,就可以在业务层中统一使用logger进行输出。
4.错误处理
在grpc中,比较通用的一种错误处理方式就是直接通过 proto 预定义定义错误码,然后通过 proto-gen-go 生成帮助代码,直接返回 error。
{
// 错误码,跟 http-status 一致,并且在 grpc 中可以转换成 grpc-status
"code": 500,
// 错误原因,定义为业务判定错误码
"reason": "user_not_found",
// 错误信息,为用户可读的信息,可作为用户提示内容
"message": "invalid argument error",
// 错误元信息,为错误添加附加可扩展信息
"metadata": {
"foo": "bar"
}
}
这里可以发现,为了兼容grpc,在http的返回结果中,code也无法自定义,只能跟随httpcode。所以这里客户端或者第三方去处理错误时,需要判断reason字段。
5.配置管理
使用proto文件定义配置和生成struct,然后将yaml中的内容读取到对应struct 字段中进行使用。
在这里我们可以注意到,在kratos中,除了传输格式使用了proto进行定义之外,错误处理和配置管理,也使用了proto来进行。可以说,一切皆proto。
6.wire
wire 是一个灵活的依赖注入工具(需要安装),通过自动生成代码的方式在编译期完成依赖注入。通过 wire 进行初始化代码,可以很好地解决组件之间的耦合,以及提高代码维护性。
打开kratos的示例项目,从main入口看,有一处调用了wireapp方法,这里就是一切的源头(万恶之源)。
这个方法调用的是main同目录的wire文件中的wireapp方法,同目录的wire_gen.go实现了此方法。
wire_gen中去实例化不同service和组建的对象,用于调用。关系图如下:


server -> service -> biz -> data
main.go -> wire.go(wire_gen.go)

wire.go 中有用到providerset
package main
import (
"kratos-demo03/internal/biz"
"kratos-demo03/internal/conf"
"kratos-demo03/internal/data"
"kratos-demo03/internal/server"
"kratos-demo03/internal/service"
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/log"
"github.com/google/wire"
)
// wireapp init kratos application.
func wireapp(*conf.server, *conf.data, log.logger) (*kratos.app, func(), error) {
panic(wire.build(server.providerset, data.providerset, biz.providerset, service.providerset, newapp))
}
main.go 有用到app和config
package main
import (
"flag"
"os"
"kratos-demo03/internal/conf"
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/config"
"github.com/go-kratos/kratos/v2/config/file"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware/tracing"
"github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/go-kratos/kratos/v2/transport/http"
_ "go.uber.org/automaxprocs"
)
// go build -ldflags "-x main.version=x.y.z"
var (
// name is the name of the compiled software.
name string
// version is the version of the compiled software.
version string
// flagconf is the config flag.
flagconf string
id, _ = os.hostname()
)
func init() {
flag.stringvar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml")
}
func newapp(logger log.logger, gs *grpc.server, hs *http.server) *kratos.app {
return kratos.new(
kratos.id(id),
kratos.name(name),
kratos.version(version),
kratos.metadata(map[string]string{}),
kratos.logger(logger),
kratos.server(
gs,
hs,
),
)
}
func main() {
flag.parse()
logger := log.with(log.newstdlogger(os.stdout),
"ts", log.defaulttimestamp,
"caller", log.defaultcaller,
"service.id", id,
"service.name", name,
"service.version", version,
"trace.id", tracing.traceid(),
"span.id", tracing.spanid(),
)
c := config.new(
config.withsource(
file.newsource(flagconf),
),
)
defer c.close()
if err := c.load(); err != nil {
panic(err)
}
var bc conf.bootstrap
if err := c.scan(&bc); err != nil {
panic(err)
}
app, cleanup, err := wireapp(bc.server, bc.data, logger)
if err != nil {
panic(err)
}
defer cleanup()
// start and wait for stop signal
if err := app.run(); err != nil {
panic(err)
}
}
在每个模块中,只需要一个 providerset 提供者集合,就可以在 wire 中进行依赖注入。
有一个数据库连接对象,service需要操作数据库,依赖数据库连接对象。这时候我们可以声明数据库连接对象在providerset集合,然后在service对象处声明,我需要一个数据库连接对象。 然后我们使用wire工具,就可以自动帮我们生成依赖注入的代码。
这里的依赖注入让代码间的依赖关系一目了然。只需要查看wire_gen.go代码就可以了解依赖关系。
发表评论