【自用 三层代码结构】「go语言」项目中降低服务内的耦合性使用wire框架或工厂模式进行管理

news2024/11/17 23:44:49

文章目录

  • 前言
  • 一、为什么需要分层?
    • 1. Server服务(向外暴露gRPC接口):
    • 2. API服务(向外暴露HTTP接口):
    • 3. domain模型的概念:
      • DO(Domain Object):
      • DTO(Data Transfer Object):
  • 二、使用wire框架管理 进行controller、service和data层的解耦
    • 1. data层
    • 2. service层
    • 3. controller层
    • wire框架编写:
  • 三、使用工厂模式管理 进行controller、service和data层的解耦
    • 1. data层
    • 2. service层


前言

在Go语言中,通常采用三层代码结构进行代码分层,即分别为表示层(Controller)、业务逻辑层(Service)和数据访问层(Data)。下面分别介绍这三层的作用和特点。

表示层(Controller)
表示层是与用户交互的界面,主要负责接收用户输入、展示数据结果和处理界面事件等。在Go语言中,通常使用HTTP协议作为前后端交互的方式,因此在表示层中,通常会使用Go语言的Web框架(如gin、echo等)进行处理。

业务逻辑层(Service)
业务逻辑层是整个应用程序的核心,主要负责处理业务逻辑和数据处理。在这一层中,通常会定义一些结构体、接口和方法等,用于对数据进行处理和操作,例如实现数据验证、业务计算、数据转换等。同时,业务逻辑层也需要调用数据访问层中的方法来读取或写入数据。

数据访问层(Data)
数据访问层负责与数据存储系统(如关系型数据库、非关系型数据库等)进行交互,实现数据的读取、写入和修改等操作。在这一层中,通常会定义一些结构体、接口和方法等,用于对数据进行操作。数据访问层也需要与业务逻辑层进行交互,以满足业务需求。


一、为什么需要分层?

现在我想换一个rpc,zrpc
逻辑和rpc的数据耦合了数据层,我们和gorm耦合了我们很有可能会遇到一下方面的问题:
rpc我们可能会换
底层orm可能会面临两个问题: 1。我们想优化性能,我想优化goods列表的查询性能,我们查询现在是从es中查询的,后面我们想要从hbase中进行查询
web:
用了gin,我们想换了krqtosihttp go-zerohttp服务
注册中心想换,consul我们想换成nacos,我们想换成k8s的服务发现和注册
缓存想换redis,后面我们可能想使用内存memcache

在Go语言中,对于服务器服务(Server Service)和API服务(API Service),常采用以下分层设计:

1. Server服务(向外暴露gRPC接口):

  • 表示层(Controller):处理gRPC请求,负责解析输入参数、验证数据等。
  • 业务逻辑层(Service):包含服务器的核心业务逻辑,独立于表示层和数据访问层。处理业务规则、数据处理和算法等操作。
  • 数据访问层(Data):与数据存储进行交互,如数据库、缓存等。提供对数据的持久化操作,封装数据库访问细节。

2. API服务(向外暴露HTTP接口):

  • 表示层(Controller):处理HTTP请求,负责解析输入参数、验证数据等。
  • 业务逻辑层(Service):包含API服务的核心业务逻辑,独立于表示层和数据访问层。处理业务规则、数据处理和算法等操作。
  • 数据访问层(Data):与数据存储进行交互,如使用其他服务的接口(例如gRPC接口)。

这种分层架构设计的好处在于,可以将服务器服务和API服务的代码分离,使得它们可以独立演进和变更。每个服务内部都遵循三层架构,以降低代码之间的耦合度。同时,表示层负责与外部接口进行交互,业务逻辑层处理核心业务逻辑,数据访问层负责与数据存储进行交互。这样的设计使得代码更加可维护、可扩展和可测试。

当需要进行更改时,这种分层设计提供了一定的灵活性:

  • 对于Server服务,如果想要更换RPC框架(例如从gRPC切换到zRPC),只需修改表示层和可能涉及到的数据访问层,而业务逻辑层可以保持不变。
  • 对于API服务,如果想要更换HTTP框架(例如从Gin切换到Kratos或Go-Zero的HTTP服务),同样只需修改表示层和可能涉及到的数据访问层,而业务逻辑层可以保持不变。
  • 数据访问层,如果需要更换底层ORM(例如从GORM切换到其他ORM),只需在数据访问层中进行相应的修改,而其他层的代码不受影响。

这种分层架构的设计使得系统更加灵活,可以方便地适应不同的框架和技术变化,同时提供了良好的代码可维护性、可扩展性和可测试性。

更重要的是方便写单元测试 因为只需要实现接口即可

3. domain模型的概念:

DO(Domain Object):

DO代表领域对象,它是在领域层(Domain Layer)中使用的对象,用于表示业务领域中的核心概念和实体。DO通常包含业务逻辑和状态,并且与特定领域的业务规则密切相关。它们被设计为与业务领域的问题域相对应,具有高度的内聚性和自洽性。

DO负责封装和表示领域的业务概念,例如用户、订单、商品等。它们通常包含了实体的属性和方法,以及处理业务规则和行为的逻辑。在应用程序中,DO在领域层中起到核心角色,负责实现业务逻辑并进行状态管理。

DTO(Data Transfer Object):

DTO代表数据传输对象,它用于在不同层之间传输数据。DTO是一种纯粹的数据结构,通常不包含业务逻辑。它的主要目的是在不同的层(如表示层、服务层和数据访问层)之间传递数据,并充当数据交换的载体。

DTO通常用于解耦不同层之间的数据传递,以避免直接暴露领域对象或数据库实体。DTO可以根据需求定制,只包含需要传递的特定数据字段,而不是将整个领域对象或实体传输过程中的所有属性都暴露出来。这样可以提高性能并减少不必要的数据传输。

DTO还可以用于将数据从领域对象转换为外部服务(如API接口)所需的格式,或者将数据从外部服务转换为领域对象所需的格式。这样的转换过程可以通过映射或转换函数来实现。

总结:
DO(领域对象)用于表示业务领域中的核心概念和实体,包含业务逻辑和状态,是领域层的核心对象。DTO(数据传输对象)用于在不同层之间传递数据,是纯粹的数据结构,没有业务逻辑。DTO可以实现不同层之间的数据解耦和格式转换。这两个概念在代码分层中有助于提高代码的可维护性、可扩展性和可测试性。

没有提供启动app的接口 需自己实现

二、使用wire框架管理 进行controller、service和data层的解耦

以商品服务为例
架构:

在这里插入图片描述

1. data层

user.go
负责gorm表结构定义和interface接口定义

package v1

import (
	metav1 "NewGo/pkg/common/meta/v1"
	"context"
	"gorm.io/gorm"
	"time"
)

type BaseModel struct {
	ID        int32          `gorm:"primary_key;comment:ID"`
	CreatedAt time.Time      `gorm:"column:add_time;comment:创建时间"`
	UpdatedAt time.Time      `gorm:"column:update_time;comment:更新时间"`
	DeletedAt gorm.DeletedAt `gorm:"comment:删除时间"`
	IsDeleted bool           `gorm:"comment:是否删除"`
}
type UserDO struct {
	BaseModel
	Mobile   string     `gorm:"index:idx_mobile;unique;type:varchar(11);not null;comment:手机号"`
	Password string     `gorm:"type:varchar(100);not null;comment:密码"`
	NickName string     `gorm:"type:varchar(20);comment:账号名称"`
	Birthday *time.Time `gorm:"type:datetime;comment:出生日期"`
	Gender   string     `gorm:"column:gender;default:male;type:varchar(6);comment:femail表示女,male表示男"`
	Role     int        `gorm:"column:role;default:1;type:int;comment:1表示普通用户,2表示管理员"`
}

func (u *UserDO) TableName() string {
	return "user"
}

type UserDOList struct {
	TotalCount int64     `json:"totalCount,omitempty"` //总数
	Items      []*UserDO `json:"data"`
}

type UserStore interface {
	/*
		有数据访问的方法,一定要有error
		参数中最好有ctx 可能需要cancel / telemetry等
	*/

	//用户列表 - 后台管理系统
	List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*UserDOList, error)

	//通过手机号码查询用户
	GetByMobile(ctx context.Context, mobile string) (*UserDO, error)

	//通过用户ID查询用户
	GetByID(ctx context.Context, id uint64) (*UserDO, error)

	//创建用户
	Create(ctx context.Context, user *UserDO) error

	//更新用户
	Update(ctx context.Context, user *UserDO) error
}

db下的文件负责实现接口和实现数据(可以是mysql)连接(可以是其他连接)

mysql.go负责连接:

package db

import (
	"NewGo/pkg/errors"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"sync"

	"NewGo/newMain/pkg/options"
)

var (
	dbFactory *gorm.DB
	once      sync.Once
)

// 这个方法会返回gorm连接
// 还不够
// 这个方法应该返回的是全局的一个变量,如果一开始的时候没有初始化好,那么就初始化一次,后续呢直接拿到这个变量
// 单例模式  演进成 sync.Once
func GetDBFactoryOr(mysqlOpts *options.MySQLOptions) (*gorm.DB, error) {
	if mysqlOpts == nil && dbFactory == nil {
		return nil, fmt.Errorf("failed to get mysql store fatory")
	}
	var err error
	once.Do(func() {
		dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
			mysqlOpts.Username,
			mysqlOpts.Password,
			mysqlOpts.Host,
			mysqlOpts.Port,
			mysqlOpts.Database)
		dbFactory, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
		if err != nil {
			return
		}
		sqlDB, _ := dbFactory.DB()
		//允许连接多少个mysql
		sqlDB.SetMaxOpenConns(mysqlOpts.MaxOpenConnections)
		//允许最大的空闲的连接数
		sqlDB.SetMaxIdleConns(mysqlOpts.MaxIdleConnections)
		//重用连接的最大时长
		sqlDB.SetConnMaxLifetime(mysqlOpts.MaxConnectionLifetime)

	})
	if dbFactory == nil || err != nil {
		return nil, errors.WithCode(101, "failed to get mysql store factory")
	}
	return dbFactory, nil

}

user.go负责实现接口逻辑:

package db

import (
	"context"

	"gorm.io/gorm"

	dv1 "NewGo/newMain/internal/data/v1"
	metav1 "NewGo/pkg/common/meta/v1"
)

type users struct {
	db *gorm.DB
}

func NewUsers(db *gorm.DB) dv1.UserStore {
	return &users{db: db}
}

func (u *users) List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*dv1.UserDOList, error) {
	panic("not implemented")
}

func (u *users) GetByMobile(ctx context.Context, mobile string) (*dv1.UserDO, error) {
	panic("not implemented")
}

func (u *users) GetByID(ctx context.Context, id uint64) (*dv1.UserDO, error) {
	panic("not implemented")
}

func (u *users) Create(ctx context.Context, user *dv1.UserDO) error {
	panic("not implemented")
}

func (u *users) Update(ctx context.Context, user *dv1.UserDO) error {
	panic("not implemented")
}

func newUsers(db *gorm.DB) *users {
	return &users{db: db}
}

// 校验  函数签名
var _ dv1.UserStore = &users{}

2. service层

负责整个业务逻辑的编写 不管data层是如何实现的 直接调用

user.go

package v1

import (
	dv1 "NewGo/newMain/internal/data/v1"
	metav1 "NewGo/pkg/common/meta/v1"
	"NewGo/pkg/errors"
	"context"
)

type UserSrv interface {
	List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*UserDTOList, error)
	Create(ctx context.Context, user *UserDTO) error
	Update(ctx context.Context, user *UserDTO) error
	GetByID(ctx context.Context, ID uint64) (*UserDTO, error)
	GetByMobile(ctx context.Context, mobile string) (*UserDTO, error)
}

// service层的管理器
type userService struct {
	//持有data层接口
	userStore dv1.UserStore
}

func (u *userService) Create(ctx context.Context, user *UserDTO) error {
	//一个手机号码只能创建一个用户,且只能通过手机号码创建。判断用户是否存在
	_, err := u.userStore.GetByMobile(ctx, user.Mobile)
	//只有手机号不存在的情况下才能注册
	if err != nil && errors.IsCode(err, 404) {
		return u.userStore.Create(ctx, &user.UserDO)
	}
	//这里应该区别到底是什么错误,访问错误?存在错误?
	return errors.WithCode(404, "用户已经存在")

	//return u.userStore.Create(ctx, &user.UserDO)
}

func (u *userService) Update(ctx context.Context, user *UserDTO) error {
	//先查询用户是否存在 其实可以不用查询 update一般不会报错
	_, err := u.userStore.GetByID(ctx, uint64(user.ID))
	if err != nil {
		return err
	}
	return u.userStore.Update(ctx, &user.UserDO)
}

func (u *userService) GetByID(ctx context.Context, ID uint64) (*UserDTO, error) {
	userDO, err := u.userStore.GetByID(ctx, ID)
	if err != nil {
		return nil, err
	}
	return &UserDTO{*userDO}, nil
}

func (u *userService) GetByMobile(ctx context.Context, mobile string) (*UserDTO, error) {
	userDO, err := u.userStore.GetByMobile(ctx, mobile)
	if err != nil {
		return nil, err
	}
	return &UserDTO{*userDO}, nil
}

var _ UserSrv = &userService{}

func NewuserService(us dv1.UserStore) UserSrv {
	return &userService{
		userStore: us,
	}
}

func (u *userService) List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*UserDTOList, error) {
	doList, err := u.userStore.List(ctx, orderby, opts)
	if err != nil {
		return nil, err
	}
	//业务逻辑2
	//代码方便写单元测试用例
	var userDTOList UserDTOList
	for _, value := range doList.Items {
		projectDTO := UserDTO{*value}
		userDTOList.Items = append(userDTOList.Items, &projectDTO)
	}
	//业务逻辑3
	return &userDTOList, nil
}

type UserDTO struct {
	//不应该这样做,目前这样做是因为跟底层的逻辑一致了。减少冗余而已
	dv1.UserDO
}
type UserDTOList struct {
	TotalCount int64      `json:"totalCount,omitempty"` //总数
	Items      []*UserDTO `json:"data"`
}

3. controller层

现在是向外提供gRPC接口
这里就不提供proto所需文件了

user.go 直接使用gRPC所生成的接口(偷懒做法)也可以自己写接口
其他文件实现即可

package user

import (
	v1 "NewGo/api/user/v1"
	srv1 "NewGo/newMain/internal/service/v1"
)

type userServer struct {
	v1.UnimplementedUserServer
	srv srv1.UserSrv
}


// java中的ioc,AutoWire,控制反转(ioc,ioc=injection of control)
// 代码分层,第三方服务(rpc,redis)各种服务,可以使用控制反转   带来了一定的复杂度
func NewUserServer(srv srv1.UserSrv) v1.UserServer {

	return &userServer{srv: srv}
}

var _ v1.UserServer = &userServer{}

controller层依赖了service层,service层依赖了data层:
contoller层能否直接依赖data层:可以的
contoller依赖service层,并不是直接依赖了具体的struct 而是依赖了interface,
但是底层是绝对不能依赖父层的!

wire框架编写:

//go:build wireinject
// +build wireinject

package srv

import (
	"github.com/google/wire"
	"jmicro/app/pkg/options"
	"jmicro/app/user/srv/internal/controller/user"
	"jmicro/app/user/srv/internal/data/v1/db"
	"jmicro/app/user/srv/internal/dataSearch/v1/es"
	v1 "jmicro/app/user/srv/internal/service/v1"
	gapp "jmicro/jmicro/app"
	"jmicro/pkg/log"
)

func initApp(*options.EsOptions, *options.MySQLOptions, *options.ServerOptions, *options.TelemetryOptions, *options.NacosConfig, *options.RegistryOptions, *log.Options) (*gapp.App, error) {
	wire.Build(ProviderSet, user.ProviderSet, v1.ProviderSet, db.ProviderSet, es.ProviderSet)
	return &gapp.App{}, nil
}

运行 go generate 命令,可以触发代码生成器的执行过程,生成所需的依赖注入代码
生成wire_gen.go静态文件

顶层调用:

userApp, err := initApp(cfg.EsOptions, cfg.MySQLOptions, cfg.Server, cfg.Telemetry, cfg.NacosOptions, cfg.Registry, cfg.Log)

三、使用工厂模式管理 进行controller、service和data层的解耦

根据上面的代码进行改造

目录结构:
在这里插入图片描述

1. data层

data.go

DataFactory 接口在 v1 包中定义。它声明了一组用于访问和操作用户信息的方法。该接口用于提供与底层数据存储交互的抽象层,这个数据存储可以是数据库或任何其他数据源。

DataFactory 接口中定义的方法如下:

User() UserStore:该方法返回一个 UserStore 接口。UserStore 是一个用于用户存储的接口,它定义了一组与用户相关的数据访问方法。

package v1

import (
	"gorm.io/gorm"
)

type DataFactory interface {
	User() UserStore

	Begin() *gorm.DB
}

user.go

UserStore 接口是 v1 包中定义的一个接口,用于定义与用户数据存储相关的方法。该接口提供了一组操作用户数据的方法,包括查询、创建和更新用户信息等操作。

package v1

import (
	metav1 "NewGo/pkg/common/meta/v1"
	"context"
	"gorm.io/gorm"
	"time"
)

type BaseModel struct {
	ID        int32          `gorm:"primary_key;comment:ID"`
	CreatedAt time.Time      `gorm:"column:add_time;comment:创建时间"`
	UpdatedAt time.Time      `gorm:"column:update_time;comment:更新时间"`
	DeletedAt gorm.DeletedAt `gorm:"comment:删除时间"`
	IsDeleted bool           `gorm:"comment:是否删除"`
}
type UserDO struct {
	BaseModel
	Mobile   string     `gorm:"index:idx_mobile;unique;type:varchar(11);not null;comment:手机号"`
	Password string     `gorm:"type:varchar(100);not null;comment:密码"`
	NickName string     `gorm:"type:varchar(20);comment:账号名称"`
	Birthday *time.Time `gorm:"type:datetime;comment:出生日期"`
	Gender   string     `gorm:"column:gender;default:male;type:varchar(6);comment:femail表示女,male表示男"`
	Role     int        `gorm:"column:role;default:1;type:int;comment:1表示普通用户,2表示管理员"`
}

func (u *UserDO) TableName() string {
	return "user"
}

type UserDOList struct {
	TotalCount int64     `json:"totalCount,omitempty"` //总数
	Items      []*UserDO `json:"data"`
}

type UserStore interface {
	/*
		有数据访问的方法,一定要有error
		参数中最好有ctx 可能需要cancel / telemetry等
	*/

	//用户列表 - 后台管理系统
	List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*UserDOList, error)

	//通过手机号码查询用户
	GetByMobile(ctx context.Context, mobile string) (*UserDO, error)

	//通过用户ID查询用户
	GetByID(ctx context.Context, id uint64) (*UserDO, error)

	//创建用户
	Create(ctx context.Context, user *UserDO) error

	//更新用户
	Update(ctx context.Context, user *UserDO) error
}

db数据层:
mysql.go:

dataFactory结构体是在 db 包中定义的,实现了 v1.DataFactory 接口。它持有一个 gorm.DB 实例,用于数据库操作。dataFactory 结构体提供了创建和操作用户数据存储的方法。

GetDBFactoryOr 函数是在 db 包中定义的。它接收一个 MySQLOptions 参数,并返回一个实现了 v1.DataFactory 接口的对象。该函数用于获取全局的 DataFactory 实例,并在第一次调用时进行初始化。
GetDBFactoryOr 函数中实现了单例模式,通过使用 sync.Once 来保证全局只有一个 DataFactory 实例被创建。在函数的第一次调用时,它会根据传入的 MySQLOptions 创建一个 gorm.DB 实例,并设置数据库连接池的相关参数。然后,将该 gorm.DB 实例封装到 dataFactory 结构体中,并将其赋值给全局的 data 变量。
每次调用 GetDBFactoryOr 函数时,它会返回全局的 data 变量,即一个实现了 v1.DataFactory 接口的对象。如果在函数首次调用时发生了错误,或者 MySQLOptions 参数为 nil,则会返回相应的错误。

package db

import (
	v1 "NewGo/newMain/internal/data/v1"
	"NewGo/pkg/errors"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"sync"

	"NewGo/newMain/pkg/options"
)

type dataFactory struct {
	db *gorm.DB
}

func (d *dataFactory) User() v1.UserStore {
	return newUserss(d)
}

func (d *dataFactory) Begin() *gorm.DB {
	return d.db.Begin()
}

var (
	//dbFactory *gorm.DB
	data v1.DataFactory
	once sync.Once
)

// 这个方法会返回gorm连接
// 还不够
// 这个方法应该返回的是全局的一个变量,如果一开始的时候没有初始化好,那么就初始化一次,后续呢直接拿到这个变量
// 单例模式  演进成 sync.Once
func GetDBFactoryOr(mysqlOpts *options.MySQLOptions) (v1.DataFactory, error) {
	if mysqlOpts == nil && data == nil {
		return nil, fmt.Errorf("failed to get mysql store fatory")
	}
	var err error
	once.Do(func() {
		dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
			mysqlOpts.Username,
			mysqlOpts.Password,
			mysqlOpts.Host,
			mysqlOpts.Port,
			mysqlOpts.Database)
		db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
		if err != nil {
			return
		}
		sqlDB, _ := db.DB()
		//允许连接多少个mysql
		sqlDB.SetMaxOpenConns(mysqlOpts.MaxOpenConnections)
		//允许最大的空闲的连接数
		sqlDB.SetMaxIdleConns(mysqlOpts.MaxIdleConnections)
		//重用连接的最大时长
		sqlDB.SetConnMaxLifetime(mysqlOpts.MaxConnectionLifetime)

		//服务发现
		//goodsClient := GetGoodsClient(registry)
		//invClient := GetInventoryClient(registry)
		data = &dataFactory{
			db: db,
		}

	})
	if data == nil || err != nil {
		return nil, errors.WithCode(101, "failed to get mysql store factory")
	}
	return data, nil

}

user.go:

users 结构体是在 db 包中定义的一个结构体类型。它实现了 v1.UserStore 接口,用于操作和管理用户数据。

package db

import (
	"context"
	"gorm.io/gorm"

	dv1 "NewGo/newMain/internal/data/v1"
	metav1 "NewGo/pkg/common/meta/v1"
)

//var PerviderSet = wire.NewSet(NewUsers, GetDBFactoryOr)

type users struct {
	db *gorm.DB
}

//	func NewUsers(db *gorm.DB) dv1.UserStore {
//		return &users{db: db}
//	}
func newUserss(fc *dataFactory) *users {
	return &users{db: fc.db}
}
func (u *users) List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*dv1.UserDOList, error) {
	panic("not implemented")
}

func (u *users) GetByMobile(ctx context.Context, mobile string) (*dv1.UserDO, error) {
	panic("not implemented")
}

func (u *users) GetByID(ctx context.Context, id uint64) (*dv1.UserDO, error) {
	panic("not implemented")
}

func (u *users) Create(ctx context.Context, user *dv1.UserDO) error {
	panic("not implemented")
}

func (u *users) Update(ctx context.Context, user *dv1.UserDO) error {
	panic("not implemented")
}

func newUsers(db *gorm.DB) *users {
	return &users{db: db}
}

// 校验  函数签名
var _ dv1.UserStore = &users{}

2. service层

service.go:

ServiceFactory 接口定义了服务工厂的方法,用于获取服务实例。
service 结构体是在 v1 包中定义的一个结构体类型,实现了 ServiceFactory 接口。它用于管理服务层的逻辑,对外提供一组用户相关的服务。

package v1

import v1 "NewGo/newMain/internal/data/v1"

type ServiceFactory interface {
	User() UserSrv
}

type service struct {
	data v1.DataFactory
	//dtmOpts *options.DtmOptions
}

func (s *service) User() UserSrv {
	return newUserServices(s)
}

var _ ServiceFactory = &service{}

func NewService(data v1.DataFactory) *service {
	return &service{data: data}
}

user.go

userService 是在 v1 包中定义的结构体,实现了 UserSrv 接口。它是服务层中针对用户操作的具体实现。
UserSrv 接口定义了一组对用户进行操作的方法,例如创建用户、更新用户、根据用户ID查询用户等。userService 结构体实现了这些方法,并通过调用数据层的接口来实现具体的用户操作逻辑。

package v1

import (
	dv1 "NewGo/newMain/internal/data/v1"
	metav1 "NewGo/pkg/common/meta/v1"
	"NewGo/pkg/errors"
	"context"
)

//var PerviderSet = wire.NewSet(NewuserService)

type UserSrv interface {
	List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*UserDTOList, error)
	Create(ctx context.Context, user *UserDTO) error
	Update(ctx context.Context, user *UserDTO) error
	GetByID(ctx context.Context, ID uint64) (*UserDTO, error)
	GetByMobile(ctx context.Context, mobile string) (*UserDTO, error)
}

// service层的管理器
type userService struct {
	userStore ServiceFactory
}

var _ UserSrv = &userService{}

//	func NewuserService(us dv1.UserStore) UserSrv {
//		return &userService{
//			userStore: us,
//		}
//	}
func newUserServices(d *service) *userService {
	return &userService{userStore: d}
}

func (u *userService) Create(ctx context.Context, user *UserDTO) error {
	//一个手机号码只能创建一个用户,且只能通过手机号码创建。判断用户是否存在
	_, err := u.userStore.User().GetByMobile(ctx, user.Mobile)
	//只有手机号不存在的情况下才能注册
	if err != nil && errors.IsCode(err, 404) {
		return u.userStore.User().Create(ctx, user)
	}
	//这里应该区别到底是什么错误,访问错误?存在错误?
	return errors.WithCode(404, "用户已经存在")

	//return u.userStore.Create(ctx, &user.UserDO)
}

func (u *userService) Update(ctx context.Context, user *UserDTO) error {
	//先查询用户是否存在 其实可以不用查询 update一般不会报错
	_, err := u.userStore.User().GetByID(ctx, uint64(user.ID))
	if err != nil {
		return err
	}
	return u.userStore.User().Update(ctx, user)
}

func (u *userService) GetByID(ctx context.Context, ID uint64) (*UserDTO, error) {
	userDO, err := u.userStore.User().GetByID(ctx, ID)
	if err != nil {
		return nil, err
	}
	return userDO, nil
}

func (u *userService) GetByMobile(ctx context.Context, mobile string) (*UserDTO, error) {
	userDO, err := u.userStore.User().GetByMobile(ctx, mobile)
	if err != nil {
		return nil, err
	}
	return userDO, nil
}

func (u *userService) List(ctx context.Context, orderby []string, opts metav1.ListMeta) (*UserDTOList, error) {
	doList, err := u.userStore.User().List(ctx, orderby, opts)
	if err != nil {
		return nil, err
	}
	//业务逻辑2
	//代码不方便写单元测试用例
	var userDTOList UserDTOList
	for _, value := range doList.Items {
		projectDTO := value
		userDTOList.Items = append(userDTOList.Items, projectDTO)
	}
	//业务逻辑3
	return &userDTOList, nil
}

type UserDTO struct {
	//不应该这样做,目前这样做是因为跟底层的逻辑一致了。减少冗余而已
	dv1.UserDO
}
type UserDTOList struct {
	TotalCount int64      `json:"totalCount,omitempty"` //总数
	Items      []*UserDTO `json:"data"`
}
  1. controller层

这一层向上提供gRPC接口 不需要工厂模式 直接持有ServiceFactory即可

package user

import (
	v1 "NewGo/api/user/v1"
	srv1 "NewGo/newMain/internal/service/v1"
)

//var PerviderSet = wire.NewSet(NewUserServer)

type userServer struct {
	v1.UnimplementedUserServer
	srv srv1.ServiceFactory
}

func NewUserServer(srv srv1.ServiceFactory) v1.UserServer {

	return &userServer{srv: srv}
}

var _ v1.UserServer = &userServer{}

串行调用:

这段代码定义了一个名为 NewUserRPCServer 的函数,用于创建用户服务的 gRPC 服务器。

函数中的步骤如下:

通过调用 db.GetDBFactoryOr(cfg.MySQLOptions) 获取数据库工厂实例 dataFactory。该方法会使用给定的 MySQL 配置信息创建并返回一个实现了 v1.DataFactory 接口的对象。
使用 dataFactory 创建服务工厂实例 serviceFactory,调用 v1.NewService(dataFactory) 进行创建。
调用 user.NewUserServer(serviceFactory) 创建用户服务的 gRPC 服务器。该方法会传入 serviceFactory 对象,并根据服务工厂创建一个实现了 v1.UserServer 接口的对象。
返回 nil 表示函数执行成功。
这段代码的作用是在初始化过程中,创建用户服务的 gRPC 服务器,并将其注册到应用程序中。通过调用 NewUserRPCServer 函数,可以完成用户服务的初始化和启动。

rpc.go

package internal

import (
	"NewGo/newMain/internal/config"
	"NewGo/newMain/internal/controller/user"
	"NewGo/newMain/internal/data/v1/db"
	v1 "NewGo/newMain/internal/service/v1"
)

func NewUserRPCServer(cfg *config.Config) error {
	dataFactory, err := db.GetDBFactoryOr(cfg.MySQLOptions)
	if err != nil {
		return err
	}
	serviceFactory := v1.NewService(dataFactory)
	_ = user.NewUserServer(serviceFactory)

	return nil
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/604593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker安装Mysql教程(linux)

本文主要讲解如何使用Docker去安装mysql 一、搜索镜像 docker search mysql二、拉取镜像 不指定版本,默认为最新版,这里用的5.7 docker pull mysql:5.7三、创建容器(运行镜像) 1、内外都使用3306端口(确保你的宿主机3…

Umi微前端水印踩坑以及解决方案

最近公司需要在管理后台加一个水印方案~ 项目用的umi方案,以为就是改一个配置的问题,后来发现坑点还蛮多~ 希望此稳定能帮助到用umi 的你们. 一. 先来说说心路历程 坑点1 umi的水印适配只能在layout中进行配置,也就是路由配置中layout为false的页面无法配置水印,比如说登录页…

SCS【27】单细胞转录组之识别标记基因 (scran)

桓峰基因公众号推出单细胞生信分析教程并配有视频在线教程,目前整理出来的相关教程目录如下: Topic 6. 克隆进化之 Canopy Topic 7. 克隆进化之 Cardelino Topic 8. 克隆进化之 RobustClone SCS【1】今天开启单细胞之旅,述说单细胞测序的前世…

规则引擎架构-基于easy-rules

目录 概念理解实例和编码抽象出2条规则简单的规则引擎事实1的处理事实2的处理 easy-rules 规则的抽象和执行事实描述规则的抽象默认的规则 动态代理执行规则和动作规则的执行:org.jeasy.rules.core.DefaultRulesEngine#doFirepublic class RuleProxy implements Inv…

Java上进了,JDK21 要来了,并发编程再也不是噩梦了

更丝滑的并发编程模式 如果说之前的 JDK17你还觉得没必要折腾,那 JDK21确实有必要关注一下了。因为 JDK21 引入了一种新型的并发编程模式。 当前 Java 中的多线程并发编程绝对是另我们都非常头疼的一部分,感觉就是学起来难啃,用起来难用。但…

基础篇010.3 STM32驱动RC522 RFID模块之三:STM32软件模拟SPI驱动RC522

目录 1. 实验硬件及原理图 2. 利用STM32CubeMX创建MDK工程 2.1 STM32CubeMX工程创建 2.2 配置调试方式 2.3 配置时钟电路 2.4 配置时钟 2.5 配置GPIO 2.6 配置串口 2.7 项目配置 3. MDK工程驱动代码调试 3.1 按键、LED程序 3.2 SPI软件模拟程序 3.3 RC522驱动程序…

Unity制作二次元卡通渲染角色材质——1、资源分析

Unity制作二次元材质角色 回到目录 大家好,我是阿赵。 开始制作二次元角色材质之前,我觉得应该是先分析一下,我手上拿到的这个角色模型资源,总共有哪些信息是我们能用的。 所以这篇文章我不会分享具体的Shader,但我感觉…

基于RT-Thread快速上手SD NAND 虚拟文件系统

SD NAND 也称之为贴片式TF卡,贴片式SD卡,采用标准的SDIO接口,兼容SPI接口。下图所示为CS 新一代CS SD NAND NP1GCR01-AOW 大小为128M,对比128M的SD卡,可以看到贴片SD卡尺寸更小,不要SD卡座,占…

STM32杂乱笔记

问题都比较的基础和低级,仅记录一下。 问题一:stm32的某个.c文件中无法调用另一个.c中的指定变量,怎么解决? 以g_ADC_sample_vaule.Iu 为例,它是 drive_adc_info.c 里面的变量,想要在system_time_sequenc…

5.4 二叉树的性质和存储结构

博主简介:一个爱打游戏的计算机专业学生博主主页: 夏驰和徐策所属专栏:算法设计与分析 5.4.1 二叉树的性质 二叉树是一种特殊的树结构,它具有一些重要的性质: 1. 每个节点最多有两个子节点:二叉树的每个…

JavaScript:setInterval() 用法详解

文章目录 1 基本语法2 参数说明3 使用示例4 停止 setInterval() 方法 1 基本语法 setInterval() 是 JavaScript 中的一个内置函数,它用于在指定的间隔时间内重复执行一段代码,实现周期性操作。该函数的语法如下: setInterval(function, mil…

线程(Linux系统实现)

目录 1. 线程概述 2.主线程和子线程 3.创建线程 线程函数 创建线程示例 4.线程退出 线程退出的原理主要包括以下两个方面: 5.线程回收 回收子线程数据 6.线程分离 7.线程取消 8.线程 ID 比较 1. 线程概述 线程是轻量级的进程(LWP&#xff…

【Java多线程进阶】常见的锁策略

前言 众所周知,拳击运动员是要分等级(轻量级、重量级等等)来参加比赛的,在 Java 多线程中 锁(synchronized) 也会根据锁的竞争程度来升级为相关“高等级”锁,为了更好的理解 synchronized 加锁机…

微信小程序node+vue医院挂号预约系统fun17

从而实现管理员后端;首页、个人中心、用户管理、专家管理、科室类型管理、职称类型管理、医院挂号管理、挂号信息管理、留言板管理、系统管理,专家后端;首页、个人中心、医院挂号管理、挂号信息管理、系统管理,用户前端&#xff1…

【Linux】网络基础+UDP网络套接字编程

只做自己喜欢做的事情,不被社会和时代裹挟着前进,是一件很奢侈的事。 文章目录 一、 网络基础1.局域网和广域网2.协议初识和网络协议分层(TCP/IP四层模型)3.MAC地址和IP地址(子网掩码,路由表,I…

美国金融科技公司SoFi的增长难以持久,股价也将下跌

来源:猛兽财经 作者:猛兽财经 公司介绍 SoFi Technologies(SoFi)是一家来自美国的知名金融科技公司,自2011年成立以来,已成为领先的个人理财在线平台。SoFi为年轻的高收入客户提供多样化的产品和服务,包括学生和汽车贷…

如何在 Python 中使用断点调试

入门教程、案例源码、学习资料、读者群 请访问: python666.cn 实际上没人能一次就写出完美的代码,除了我。但是世界上只有一个我。 林纳斯托瓦兹(Linux 之父) 大家好,欢迎来到 Crossin的编程教室 ! 上面这段…

【CSS3系列】第二章 · CSS3 新增盒模型和背景属性

写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正&#xff0…

大数据:数据表操作,分区表,分桶表,修改表,array,map, struct

大数据:数据表操作,分区表 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测开 测开的话,你就得学数据库,sql,oracle&a…

【能量算子】评估 EEG 中的瞬时能量:非负、频率加权能量算子(PythonMatlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…