golang项目三层依赖架构,自底向上;依赖注入trpc\grpc

1. repo 层面

1.1 依赖、业务逻辑部分

  • repo层的再底层依赖是数据库db,因此需要结构体的成员包含一个依赖Dependency,该依赖的内容DBGetter可由于外部传入
  • 将依赖初始化封装函数(上层调用进行底层初始化)
  • 业务逻辑,需要封装好返回的结构体
go 复制代码
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等)==的方法了
go 复制代码
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的地方先分析逻辑

go 复制代码
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中的多态特点!
go 复制代码
// Dependency 表示用户服务初始化依赖
type Dependency interface {
	// QueryAccountByUsername 通过用户名查询帐户信息, 如果帐户不存在则返回 (nil, nil)
	QueryAccountByUsername(ctx context.Context, username string) (*entity.Account, error)
	// 后续还有很多其他方法...
}

type userImpl struct {
	dep Dependency
}
  • 最后,在service的业务逻辑中,GetAccountByUsername方法调用底层的QueryAccountByUsername即可。
    完整代码如下:
go 复制代码
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

相关推荐
SofterICer4 分钟前
GSMA eUICC 远程配置架构技术规范笔记 (SGP.02 v3.2)
笔记·架构
Little At Air14 分钟前
C++之红黑树
开发语言
声网42 分钟前
「AI Infra 软件开源不是一个选项,而是必然」丨云边端架构和 AI Infra专场回顾@RTE2024
人工智能·架构·开源
yangmc041 小时前
判断子序列
开发语言·数据结构·c++·算法·矩阵·图论
昇腾CANN1 小时前
Ascend C算子性能优化实用技巧05——API使用优化
c语言·开发语言·性能优化
y25081 小时前
《抽象类和接口》
java·开发语言
沐泽Mu1 小时前
嵌入式学习-C嘎嘎-Day03
c语言·开发语言·c++·学习
小华同学ai2 小时前
WukongCRM:github高分开源项目,基于微服务架构 +vue ElementUI的前后端分离CRM系统
架构·开源·github
chunmiao30322 小时前
自建k8s集群,利用开源的GitLab、Jenkins和Harbor实现CI/CD和DevOps的过程回顾
java·开发语言
gorgor在码农2 小时前
Redis 高并发缓存架构实战与性能优化
redis·缓存·架构