文章目录
- 前言
- 一、为什么需要分层?
- 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-zero的http服务
注册中心想换,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"`
}
- 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
}