golang项目三层依赖架构,自底向上;依赖注入trpc\grpc
1. repo 层面
1.1 依赖、业务逻辑部分
- repo层的再底层依赖是数据库db,因此需要结构体的成员包含一个依赖Dependency,该依赖的内容DBGetter可由于外部传入
- 将依赖初始化封装函数(上层调用进行底层初始化)
- 业务逻辑,需要封装好返回的结构体
package account
import (
"context"
"fmt"
"github.com/SIN5t/tRPC-go/app/user/entity"
"github.com/SIN5t/tRPC-go/proto/user"
)
import "trpc.group/trpc-go/trpc-database/mysql"
type UserAccountRepository struct {
dep Dependency
}
type Dependency struct {
DBGetter func(context.Context) (mysql.Client, error)
}
func (r UserAccountRepository) initUserAccountRepository(dep Dependency) error {
r.dep = dep
return nil
}
// QueryUserAccountByName 业务逻辑,实现service的interface中的方法
func (r UserAccountRepository) QueryUserAccountByName(ctx context.Context, req user.GetAccountByUserNameRequest) (*entity.Account, error) {
dbClient, err := r.dep.DBGetter(ctx)
if err != nil {
return nil, fmt.Errorf("获取DB失败(%w)", err)
}
var userAccountItems []userAccountItem
query := fmt.Sprintf("select * from %s where username = ? LIMIT 1", userAccountItem{}.TableName())
if err := dbClient.Select(ctx, &userAccountItems, query, req.GetUsername()); err != nil {
return nil, fmt.Errorf("查询db失败(%w)", err)
}
if len(userAccountItems) == 0 {
return nil, nil
}
return userAccountItems[0].ToEntity(), nil
}
1.2 统一repo仓库管理
- 如果需要每一个数据库都单独进行依赖注入,代码过于冗余,因此这个进行整个微服务统一的依赖管理
- 可以从1.1中知道,account业务逻辑初始化需要一个mysql client的DBgetter依赖,因此就从这里传进去。
- 1.1中还封装了account的初始化,这里也只要传递相关依赖,调用初始化方法,就能初始化account,这样大的Repo结构体就包含了account
- 对外封装好,并暴露了NewRepo方法,只需要传入db连接名,就会生成一个client,并完成更加底层如account等的依赖构建。
- 最后外部只需要调用NewRepo方法,完成各种底层库表依赖注入,返回的Repo结构体就可以随意调用其包含的==其他表(如account等)==的方法了
package repo
import (
"context"
"fmt"
"github.com/SIN5t/tRPC-go/app/user/repo/account"
"trpc.group/trpc-go/trpc-database/mysql"
)
type Repo struct {
account.UserAccountRepository
}
type Dependency struct {
UserAccountDBClientName string
}
// NewRepo 新建 user 服务所需的 repo 依赖全集
func NewRepo(d Dependency) (*Repo,error) {
repo := &Repo{}
// 初始化用户仓库
accountDep := account.Dependency{
DBGetter: func(ctx context.Context) (mysql.Client, error) {
return mysql.NewUnsafeClient(d.UserAccountDBClientName),nil
},
}
if err := repo.InitUserAccountRepository(accountDep); err != nil{
return nil,fmt.Errorf("初始化用户仓库失败(%w)",err)
}
return repo,nil
}
2. main
为什么先说main再说service呢?
因为上面刚刚说完,提供了一个NewRepo的封装,返回一个Repo结构体,这个结构体 表示user 服务所需的 repo 依赖全集,可以拿到user的所有服务方法,因此这里直接跳到调用NewRepo的地方先分析逻辑
package main
import (
"github.com/Andrew-M-C/trpc-go-demo/app/user/repo"
"github.com/Andrew-M-C/trpc-go-demo/app/user/service"
"trpc.group/trpc-go/trpc-go"
"trpc.group/trpc-go/trpc-go/log"
)
func main() {
s := trpc.NewServer()
r, err := initializeRepo()
if err != nil {
log.Fatalf("初始化 repo 失败: %v", err)
}
if err := service.RegisterUserService(s, r); err != nil {
log.Fatalf("注册用户服务失败: %v", err)
}
if err := s.Serve(); err != nil {
log.Fatalf("启动服务失败: %v", err)
}
}
func initializeRepo() (*repo.Repo, error) {
dep := repo.Dependency{
UserAccountDBClientName: "db.mysql.userAccount",
}
r, err := repo.NewRepo(dep)
if err != nil {
return nil, err
}
return r, nil
}
- 代码其实很简单,就是把Repo结构体New出来,用于服务注册
问题:为什么使用Repo结构体进行服务注册?
还是那个答案:Repo结构体,这个结构体 表示user 服务所需的 repo 依赖全集,可以拿到user的所有服务方法,因此只需要注册一次即可完成所有user底层管理。
service
问题又来了:proto中定义的service服务主体,GetAccountByUsername()方法是生成的接口需要实现的方法,但之前repo层实现的方法是QueryAccountByUsername()方法,想要将Repo结构体注册给服务,一定需要实现接口的方法,这下没实现,怎么办?
答:这正是service层的作用,实际上,我们必须实现proto中定义的接口,因此也很简单:在service层中搞一个结构体:userImpl,实现接口中的方法GetAccountByUsername即可!
追问:之前的Repo呢?
答:GetAccountByUsername的实现肯定也是要靠底层Repo的,因此service层的依赖就是Repo! 正好Repo还包含所有user服务的方法!
- 如何优雅实现上面的依赖呢?如下使用interface:Repo{}实现了所有repo层的方法,实际上Repo就是下面这个Dependency的一种implemence,无需显示说明,并且可以传入
- 后续这个interface再有其他方法也是Repo底层的,只要一个Repo结构体传进来就可以搞定所有,符合golang中的多态特点!
// Dependency 表示用户服务初始化依赖
type Dependency interface {
// QueryAccountByUsername 通过用户名查询帐户信息, 如果帐户不存在则返回 (nil, nil)
QueryAccountByUsername(ctx context.Context, username string) (*entity.Account, error)
// 后续还有很多其他方法...
}
type userImpl struct {
dep Dependency
}
- 最后,在service的业务逻辑中,GetAccountByUsername方法调用底层的QueryAccountByUsername即可。
完整代码如下:
package service
import (
"context"
"github.com/Andrew-M-C/trpc-go-demo/app/user/entity"
"github.com/Andrew-M-C/trpc-go-demo/proto/user"
"trpc.group/trpc-go/trpc-go/log"
"trpc.group/trpc-go/trpc-go/server"
)
// RegisterUserService 注册用户服务
func RegisterUserService(s server.Service, d Dependency) error {
impl := &userImpl{dep: d}
user.RegisterUserService(s, impl)
return nil
}
// Dependency 表示用户服务初始化依赖
type Dependency interface {
// QueryAccountByUsername 通过用户名查询帐户信息, 如果帐户不存在则返回 (nil, nil)
QueryAccountByUsername(ctx context.Context, username string) (*entity.Account, error)
}
type userImpl struct {
dep Dependency
}
// GetAccountByUserName 根据用户名获取帐户信息
func (impl *userImpl) GetAccountByUserName(
ctx context.Context, req *user.GetAccountByUserNameRequest,
) (rsp *user.GetAccountByUserNameResponse, _ error) {
rsp = &user.GetAccountByUserNameResponse{}
u, err := impl.dep.QueryAccountByUsername(ctx, req.Username)
if err != nil {
log.ErrorContextf(ctx, "查询 username '%s' 失败: %v", req.Username, err)
rsp.ErrCode = -1 // TODO: 采用规范的错误码定义
rsp.ErrMsg = err.Error()
return
}
if u == nil {
log.InfoContextf(ctx, "username '%s' 不存在", req.Username)
rsp.ErrCode = 404
rsp.ErrMsg = "用户不存在"
return
}
rsp.UserId = u.ID
rsp.Username = u.Username
rsp.PasswordHash = u.PasswordHash
rsp.ErrMsg = "success"
return
}
时间:上午3.55,睡不着码点字emmmm