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

相关推荐
十年一梦实验室12 分钟前
【C++】sophus : rxso3.hpp 实现了 3D 空间中的旋转和缩放操作的 RxSO3 类 (二十一)
开发语言·c++·人工智能·算法·3d
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ18 分钟前
MyBatis执行完sql后,返回的数值代表的意思
java·开发语言
小爬虫程序猿33 分钟前
如何利用Python爬虫精准获取苏宁易购商品详情
开发语言·爬虫·python
API快乐传递者33 分钟前
Python爬虫获取1688详情接口详细解析
开发语言·爬虫·python
Fan_55837 分钟前
008 Qt_显示类控件_QLabel
开发语言·qt
No0d1es41 分钟前
GESP CCF C++六级编程等级考试认证真题 2024年12月
开发语言·c++·算法·青少年编程·gesp·ccf·六级
IT19951 小时前
Qt笔记-Qt Creator开发环境搭建
开发语言·笔记·qt
爱上语文1 小时前
宠物管理系统:Service层
java·开发语言·宠物
意疏1 小时前
【C 语言指针篇】指针的灵动舞步与内存的神秘疆域:于 C 编程世界中领略指针艺术的奇幻华章
c语言·开发语言·指针
水w1 小时前
【项目实践】SpringBoot Nacos配置管理 map数据
java·服务器·开发语言·spring boot·nacos