golang项目三层依赖架构,自底向上;依赖注入trpc\grpc
1. repo 层面
1.1 依赖、业务逻辑部分
- repo层的再底层依赖是数据库db,因此需要结构体的成员包含一个依赖Dependency,该依赖的内容DBGetter可由于外部传入
- 将依赖初始化封装函数(上层调用进行底层初始化)
- 业务逻辑,需要封装好返回的结构体
package accountimport ("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 = depreturn 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 []userAccountItemquery := 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 repoimport ("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 mainimport ("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 serviceimport ("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 = 404rsp.ErrMsg = "用户不存在"return}rsp.UserId = u.IDrsp.Username = u.Usernamersp.PasswordHash = u.PasswordHashrsp.ErrMsg = "success"return
}
时间:上午3.55,睡不着码点字emmmm