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

相关推荐
子兮曰1 小时前
后端字段又改了?我撸了一个 BFF 数据适配器,从此再也不怕接口“屎山”!
前端·javascript·架构
卓卓不是桌桌3 小时前
如何优雅地处理 iframe 跨域通信?这是我的开源方案
javascript·架构
Qlly3 小时前
DDD 架构为什么适合 MCP Server 开发?
人工智能·后端·架构
用户881586910911 天前
AI Agent 协作系统架构设计与实践
架构
鹏北海1 天前
Qiankun 微前端实战踩坑历程
前端·架构
货拉拉技术1 天前
货拉拉海豚平台-大模型推理加速工程化实践
人工智能·后端·架构
RoyLin1 天前
libkrun 深度解析:架构设计、模块实现与 Windows WHPX 后端
架构
CoovallyAIHub2 天前
实时视觉AI智能体框架来了!Vision Agents 狂揽7K Star,延迟低至30ms,YOLO+Gemini实时联动!
算法·架构·github
RoyLin2 天前
领域驱动设计:回归本质的工程实践
架构
CoovallyAIHub2 天前
OpenClaw:从“19万星标”到“行业封杀”,这只“赛博龙虾”究竟触动了谁的神经?
算法·架构·github