biz

在kratos Blog的“Go工程化 - Project Layout 最佳实践”一文中有对biz层描述:

“业务逻辑的组装层,类似 DDD 的 domain 层,data 类似 DDD 的 repo,repo 接口在这里定义,使用依赖倒置的原则。”

啊这…看完后,我更蒙了,DDD是啥,domain又是啥,学校啥也没教我啊( ´•̥̥̥ω•̥̥̥` )

没事,我不知道,但是总有人知道。对着搜索引擎猛地一哆嗦,啪啪,很快啊,在某乎上找到“领域驱动设计(DDD)-基础思想”这篇文章,其中介绍了DDD也就是“领域驱动设计”也介绍了“领域驱动三层架构”。文中是这样介绍domian层的

“系统的核心层,所有具体的业务逻辑处理、事件处理等都在这层域模型中处理”

我完全懂了.jpg(不是) 管它呢,干就完了

模型定义

定义一个极其简单的用户模型,它的名字叫做User,它有两个属性,一个叫做名字(Name),另一个叫做密码(Passwd)。

将这个用户模型在代码中进行实现

在“/biz”目录下创建一个名为“user.go”的文件

在“/biz/user.go”文件里添加如下内容

  1. package biz
  2. import (
  3. "gorm.io/gorm"
  4. )
  5. // User 用户数据模型
  6. type User struct {
  7. // GORM的一个基础数据模型
  8. // 其中有ID、CreatedAt(创建时间)、UpdatedAt(更新时间)、DeletedAt(删除时间)
  9. gorm.Model
  10. // Name 用户的姓名
  11. // "not null" 代表不能为空
  12. // "type" 定义属性(字段)在数据库中的类型,其类型为"varchar(32)"
  13. Name string `gorm:"not null; type:varchar(32)"`
  14. // Passwd 用户的密码
  15. Passwd string `gorm:"not null; type:char(16)"`
  16. }

为了让GORM知道我们定义了一个数据模型我们需要对“/data/data.go”文件进行修改

我们在“/data/data.go”的最底下加入

  1. // DBAutoMigrate 数据库模型自动迁移
  2. func DBAutoMigrate(db *gorm.DB) error {
  3. // 在这里让GORM知道那些结构体是我们的数据模型,GORM将完成自动建表
  4. err := db.AutoMigrate(
  5. &biz.User{}, // 加入用户数据模型
  6. )
  7. if err != nil {
  8. return err
  9. }
  10. return nil
  11. }

在“/data/data.go”的NewDataBase函数里调用刚刚编写的DBAutoMigrate函数

  1. // NewDataBase 初始化数据库
  2. func NewDataBase(c *conf.Data) (*gorm.DB, error) {
  3. ......
  4. // 超时
  5. sqlDb.SetConnMaxLifetime(time.Second * 30)
  6. err = DBAutoMigrate(db)
  7. if err != nil {
  8. return nil, err
  9. }
  10. return db, nil
  11. }

到这里,我们就完成了用户数据模型的定义,并且让GORM知道我们定义了这个数据模型

repo接口的定义

在repo接口中,我们需要定义一些基础操作数据库修改数据的函数

让我们回到“/biz/user.go”,在这个文件的末尾添加以下代码

  1. // UserRepo 将“用户数据模型”在数据库上的基础操作封装为接口
  2. type UserRepo interface {
  3. // CreateUser 创建用户
  4. // 参数 model 为 User用户数据模型
  5. CreateUser(ctx context.Context, model *User) (uint, error)
  6. }

data层中实现repo接口

在“/data”目录下创建”user.go”文件

给创建好的“/data/user.go”文件添加内容

我们创建一个私有的结构体去实现“/biz/user.go”中的“biz.UserRepo”接口

  1. // userRepo 实现 biz.UserRepo 接口
  2. // 私有的结构体
  3. // 对“/biz/user.go”中的UserRepo进行实现,
  4. // 完成对“用户数据模型”的数据库基础操作。
  5. type userRepo struct {
  6. data *Data // 数据库客户端的集合
  7. log *log.Helper
  8. }

让我们编写一个“NewUseRepo”函数来创建结构体userRepo

  1. // NewUseRepo .
  2. // 注意这里返回的是 biz.UserRepo
  3. // 在golang中接口的继承是不需要明确声明的
  4. // 只要我们的私有结构体userRepo实现了“/biz/user.go”内 biz.UserRepo 接口内的方法
  5. // 那么 userRepo 就是“继承”了 biz.UserRepo
  6. func NewUseRepo(data *Data, logger log.Logger) biz.UserRepo {
  7. return &userRepo{
  8. data: data,
  9. log: log.NewHelper(logger),
  10. }
  11. }

啪!这个时候ide是不是已经爆红报错了?安心咯,这只是因为我们的“userRepo”还没有实现“biz.UserRepo”接口内的方法

在上文“repo接口的定义”中,我们给“biz.UserRepo”接口编写了一个“CreateUser”方法(成员函数),它长这样

CreateUser(ctx context.Context, model *User) (uint, error)

现在我们给“userRepo”实现这个方法

在“/data/user.go”的末尾添加

  1. // CreateUser 创建用户的数据库操作
  2. // model 为用户数据模型
  3. func (u userRepo) CreateUser(ctx context.Context, model *biz.User) (uint, error) {
  4. // 使用 “/data/date.go”中的 “Data”结构体内的 “DataBase *gorm.DB”数据库客户端在数据库中创建一个“用户数据”
  5. err := u.data.DataBase.Create(model).Error
  6. // 如果存在错误,则将错误返回
  7. if err != nil {
  8. return 0, err
  9. }
  10. // 如果没有发生错误,则返回创建成功后的用户id
  11. return model.ID, nil
  12. }

让我们吧创建结构体的函数“NewUseRepo”添加到依赖提供者集中

修改“/data/data.go”文件中的“wire.NewSet()”函数,在“NewSet”函数里添加“NewUseRepo”

  1. // ProviderSet is data providers.
  2. var ProviderSet = wire.NewSet(NewData, NewDataBase, NewUseRepo)

/biz/user.go

业务逻辑处理

我们将对用户的操作封装成“UserUseCase”结构体

回到“/biz/user.go”添加“UserUseCase”结构体

  1. // UserUseCase 将对用户的操作封装
  2. type UserUseCase struct {
  3. }

在结构体中添加“repo”成员,其类型是我们编写的repo接口

在结构体中添加“log”成员,*log.Helper类型,虽然在这里不会使用到,但也还是加上

  1. // UserUseCase 将对用户的操作封装
  2. type UserUseCase struct {
  3. repo UserRepo // 我们需要使用Repo来对数据库进行基础操作
  4. log *log.Helper
  5. }

编写一个“NewUserUseCase”去创建一个“UserUseCase”,并将它返回,以便于之后的wine依赖注入

  1. func NewUserUseCase(repo UserRepo, logger log.Logger) *UserUseCase {
  2. return &UserUseCase{
  3. repo: repo,
  4. log: log.NewHelper(logger),
  5. }
  6. }

将以上乱七八糟的东西封装好后我们可以开始真正的业务逻辑处理了

接下来进行编写创建用户的业务逻辑

不同的项目有不同的功能,不同的功能需要不同的实现,实现这些核心功能的代码就叫业务逻辑

我们在这里去使用各种底层基础设施(例如数据库,缓存,对象存储等)去储存、获取数据,对这些数据进行处理,实现项目的功能。(当然,我们目前只使用到了)

我们创建一个“UserUseCase”的方法(成员函数),命名为“CreateUser”

  1. func (uc *UserUseCase) CreateUser(ctx context.Context, name, passwd string) (uint, error) {
  2. }

如果你关心为什么这个函数有一个“ctx context.Context”参数,你可以去搜索以下“golang context”,这里我们没有使用到它,暂且放一放

至于为什么要有name, passwd呢,还记得我们用户数据模型中的Name和Passwd吗,如果忘记了可以往上翻翻,看一下“模型定义”部分

接下来我们在“CreateUser”方法中创建一个用户数据模型,并且调用“UserRepo”去完成用户数据的创建

  1. // CreateUser 创建用户
  2. func (uc *UserUseCase) CreateUser(ctx context.Context, name, passwd string) (uint, error) {
  3. // 将“创建用户”函数的参数转换为“用户数据模型”
  4. userModel := &User{
  5. Name: name,
  6. Passwd: passwd,
  7. }
  8. // 调用UserRepo接口实现中的CreateUser函数去创建用户
  9. return uc.repo.CreateUser(ctx, userModel)
  10. }

啊,没了,业务逻辑写完了。为啥?因为我们只写了一个创建用户的业务逻辑啊,多写几个业务它不就多了吗

最后,我们将“NewUserUseCase”函数加入到依赖提供者集中

修改“/biz/biz.go”文件中的“wire.NewSet()”函数,在“NewSet”函数里添加“NewUserUseCase”

  1. // ProviderSet is biz providers.
  2. var ProviderSet = wire.NewSet(NewUserUseCase)

完成本节后你将得到如下代码

文件: /data/data.go

  1. package data
  2. import (
  3. "github.com/go-kratos/kratos/v2/log"
  4. "github.com/google/wire"
  5. "gorm.io/driver/mysql"
  6. "gorm.io/gorm"
  7. "time"
  8. "user/internal/biz"
  9. "user/internal/conf"
  10. )
  11. // ProviderSet is data providers.
  12. var ProviderSet = wire.NewSet(NewData, NewDataBase, NewUseRepo)
  13. // Data .
  14. type Data struct {
  15. DataBase *gorm.DB // 数据库
  16. }
  17. // NewData .
  18. func NewData(c *conf.Data, logger log.Logger, db *gorm.DB) (*Data, func(), error) {
  19. cleanup := func() {
  20. log.NewHelper(logger).Info("closing the data resources")
  21. }
  22. return &Data{
  23. DataBase: db,
  24. }, cleanup, nil
  25. }
  26. // NewDataBase 初始化数据库
  27. func NewDataBase(c *conf.Data) (*gorm.DB, error) {
  28. // dsn 数据库链接
  29. // "用户名":"密码"@tcp("IP":"端口")/"数据库名称"?charset=utf8mb4&parseTime=True&loc=Local
  30. // 不要无脑抄作业哦,根据自己的实际情况修改数据库链接
  31. dsn := "test:test@tcp(localhost:3306)/kratos_demo?charset=utf8mb4&parseTime=True&loc=Local"
  32. db, err := gorm.Open(
  33. mysql.Open(dsn),
  34. &gorm.Config{})
  35. if err != nil {
  36. return nil, err
  37. }
  38. sqlDb, err := db.DB()
  39. if err != nil {
  40. return nil, err
  41. }
  42. // 设置连接池
  43. // 空闲
  44. sqlDb.SetMaxIdleConns(50)
  45. // 打开
  46. sqlDb.SetMaxOpenConns(100)
  47. // 超时
  48. sqlDb.SetConnMaxLifetime(time.Second * 30)
  49. err = DBAutoMigrate(db)
  50. if err != nil {
  51. return nil, err
  52. }
  53. return db, nil
  54. }
  55. // DBAutoMigrate 数据库模型自动迁移
  56. // 在这里让GORM知道那些结构体是我们的数据模型,GORM将完成自动建表
  57. func DBAutoMigrate(db *gorm.DB) error {
  58. err := db.AutoMigrate(
  59. &biz.User{}, // 加入用户数据模型
  60. )
  61. if err != nil {
  62. return err
  63. }
  64. return nil
  65. }

文件: /data/user.go

  1. package data
  2. import (
  3. "context"
  4. "github.com/go-kratos/kratos/v2/log"
  5. "user/internal/biz"
  6. )
  7. // userRepo 实现 biz.UserRepo 接口
  8. // 私有的结构体
  9. // 对“/biz/user.go”中的UserRepo进行实现,
  10. // 完成对“用户数据模型”的数据库基础操作。
  11. type userRepo struct {
  12. data *Data // 数据库客户端的集合
  13. log *log.Helper
  14. }
  15. // NewUseRepo .
  16. // 注意这里返回的是 biz.UserRepo
  17. // 在golang中接口的继承是不需要明确声明的
  18. // 只要我们的私有结构体userRepo实现了“/biz/user.go”内 biz.UserRepo 接口内的方法
  19. // 那么 userRepo 就是继承了 biz.UserRepo
  20. func NewUseRepo(data *Data, logger log.Logger) biz.UserRepo {
  21. return &userRepo{
  22. data: data,
  23. log: log.NewHelper(logger),
  24. }
  25. }
  26. // CreateUser 创建用户的数据库操作
  27. // model 为用户数据模型
  28. func (u userRepo) CreateUser(ctx context.Context, model *biz.User) (uint, error) {
  29. // 使用 “/data/date.go”中的 “Data”结构体内的 “DataBase *gorm.DB”数据库客户端在数据库中创建一个“用户数据”
  30. err := u.data.DataBase.Create(model).Error
  31. // 如果存在错误,则将错误返回
  32. if err != nil {
  33. return 0, err
  34. }
  35. // 如果没有发生错误,则返回创建成功后的用户id
  36. return model.ID, nil
  37. }

文件: /biz/user.go

  1. package biz
  2. import (
  3. "context"
  4. "github.com/go-kratos/kratos/v2/log"
  5. "gorm.io/gorm"
  6. )
  7. // User 用户数据模型
  8. type User struct {
  9. // GORM的一个基础数据模型
  10. // 其中有ID、CreatedAt(创建时间)、UpdatedAt(更新时间)、DeletedAt(删除时间)
  11. gorm.Model
  12. // Name 用户的姓名
  13. // "not null" 定义该数据库字段不能为空
  14. // "type" 定义该数据库字段的类型,其类型为"varchar(32)"
  15. Name string `gorm:"not null; type:varchar(32)"`
  16. // Passwd 用户的密码
  17. Passwd string `gorm:"not null; type:char(16)"`
  18. }
  19. // UserRepo 将“用户数据模型”在数据库上的基础操作封装为接口
  20. type UserRepo interface {
  21. // CreateUser 创建用户
  22. // 参数 model 为 User用户数据模型
  23. CreateUser(ctx context.Context, model *User) (uint, error)
  24. }
  25. // UserUseCase 将对用户的操作封装
  26. type UserUseCase struct {
  27. repo UserRepo // 我们需要使用Repo来对数据库进行基础操作
  28. log *log.Helper
  29. }
  30. func NewUserUseCase(repo UserRepo, logger log.Logger) *UserUseCase {
  31. return &UserUseCase{
  32. repo: repo,
  33. log: log.NewHelper(logger),
  34. }
  35. }
  36. // 以下开始!
  37. // 业务逻辑处理!
  38. // CreateUser 创建用户
  39. func (uc *UserUseCase) CreateUser(ctx context.Context, name, passwd string) (uint, error) {
  40. // 将“创建用户”函数的参数转换为“用户数据模型”
  41. userModel := &User{
  42. Name: name,
  43. Passwd: passwd,
  44. }
  45. // 调用UserRepo接口实现中的CreateUser函数去创建用户
  46. return uc.repo.CreateUser(ctx, userModel)
  47. }

参考内容

《GORM 指南》入门指南 - 模型定义: https://gorm.io/zh_CN/docs/models.html

《kratos Blog》Go工程化 - Project Layout 最佳实践: https://go-kratos.dev/blog/go-project-layout

领域驱动设计(DDD)-基础思想 BY Ebiubiu: https://zhuanlan.zhihu.com/p/109114670