kratos 框架商城微服务实战之商品服务 (七)

本文最后更新于:2 个月前

这篇咱们开始编写商品服务的商品类型了。一个电商的商品设计是比较复杂的,咱们这里不过多的深究表是否合理,是否漏写之类的问题,主要是为了搞明白 kratos 的使用和微服务相关的调用关系。当然我真正的编写时也会尽可能的让此项目的商品设计合理一些。但大量的表设计呀,重复性的 curd 就不会再文章中体现了,具体参看 GitHub 上的源码,当然你觉得不合理的地方,也可以给项目提 PR。

文章写的不清晰的地方可通过 GitHub 源码进行查看, 也感谢您指出不足之处,欢迎大佬指教。

注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。

商品类型

商品类型不同于商品分类,它指的是依据某一类商品的相同规格和属性归纳成的一个类型集合,例如手机类型都有机身颜色、内存大小、屏幕尺寸、铃声、网络制式等共同的规格属性;书籍类型都有出版社、作者、ISBN 号等共同的属性。

编写代码

  • 设计表字段

新增 service/goods/internal/data/good_type.go 文件;编写如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package data

import (
"context"
"github.com/go-kratos/kratos/v2/log"
"goods/internal/biz"
"gorm.io/gorm"
"time"
)

// GoodsType 商品类型表
type GoodsType struct {
ID int64 `gorm:"primarykey;type:int" json:"id"`
Name string `gorm:"type:varchar(50);not null;comment:商品类型名称" json:"name"`
TypeCode string `gorm:"type:varchar(50);not null;comment:商品类型编码" json:"type_code"`
NameAlias string `gorm:"type:varchar(50);not null;comment:商品类型别名" json:"name_alias"`
IsVirtual bool `gorm:"comment:是否是虚拟商品显示;default:false" json:"is_virtual"`
Desc string `gorm:"type:varchar(50);not null;comment:商品类型描述" json:"desc"`
Sort int32 `gorm:"comment:类型排序;default:99;not null;type:int" json:"sort"`
CreatedAt time.Time `gorm:"column:add_time" json:"created_at"`
UpdatedAt time.Time `gorm:"column:update_time" json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

type goodsTypeRepo struct {
data *Data
log *log.Helper
}

// NewGoodsTypeRepo .
func NewGoodsTypeRepo(data *Data, logger log.Logger) biz.GoodsTypeRepo {
return &goodsTypeRepo{
data: data,
log: log.NewHelper(logger),
}
}

注入 GoodsType 修改 service/goods/internal/data/data.go 文件

1
2
3
4
5
6
7
var ProviderSet = wire.NewSet(
NewData,
NewDB, NewRedis,
NewBrandRepo,
NewCategoryRepo,
NewGoodsTypeRepo,
)

⚠️ ⚠️ ⚠️ 注意: 接下来新增或修改的代码, wire 注入的文件中需要修改的代码,都不会再本文中提及了。例如 biz、service 层的修改,自己编写的过程中,千万不要忘记 wire 注入,更不要忘记,执行 make wire 命令,重新生成项目的 wire 文件 ⚠️ ⚠️ ⚠️

  • goods.proto 文件中新加入创建商品类型的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

...

import "validate/validate.proto";
...

service Goods {
...
// 商品类型基本信息创建
rpc CreateGoodsType(GoodsTypeRequest) returns(GoodsTypeResponse);
}

...

message GoodsTypeRequest {
int64 id = 1;
string name = 2 [(validate.rules).string.min_len = 3];;
string typeCode = 3 [(validate.rules).string.min_len = 3];;
string nameAlias = 4;
bool isVirtual = 5;
string desc = 6;
int32 sort = 7;
}

message GoodsTypeResponse {
int64 id = 1;
}
  • 实现 rpc 接口

新增 service/goods/internal/service/goods_type.go 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package service

import (
"context"
v1 "goods/api/goods/v1"
"goods/internal/domain"
)

func (g *GoodsService) CreateGoodsType(ctx context.Context, r *v1.GoodsTypeRequest) (*v1.GoodsTypeResponse, error) {
id, err := g.gt.GoosTypeCreate(ctx, &domain.GoodsType{
Name: r.Name,
TypeCode: r.TypeCode,
NameAlias: r.NameAlias,
IsVirtual: r.IsVirtual,
Desc: r.Desc,
Sort: r.Sort,
})
if err != nil {
return nil, err
}
return &v1.GoodsTypeResponse{
Id: id,
}, nil
}

你应该也注意到了,代码中接受不了到的值,经过 domain 进行转换了,之前都是直接通过 biz 层下面定义的参数结构体去转换。这里为什么又引入了一层 domain 呢?主要是,之前编写的服务都比较单一化,一个请求方法,只操作一张表,也没有过多的业务逻辑,但接下来商品服务会有更多的,新增商品、商品 SKU、商品属性、商品规格,一系列的业务逻辑处理,考虑到 biz 层尽量做到只编排 repo 和一些少量的业务逻辑处理,其他处理类似于参数校验呀、插入之前判断是否存在呀之类的,都放到了 domain 层去处理。

  • 新增 domain 层

新建 service/goods/internal/domain/goods_type.go 文件,编写内容如下:

1
2
3
4
5
6
7
8
9
10
11
12

package domain

type GoodsType struct {
ID int64
Name string
TypeCode string
NameAlias string
IsVirtual bool
Desc string
Sort int32
}
  • 修改 biz 层

新增 service/goods/internal/biz/goods_type.go 文件,编写内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package biz

import (
"context"
"errors"
"github.com/go-kratos/kratos/v2/log"
"goods/internal/domain"
)

type GoodsTypeRepo interface {
CreateGoodsType(context.Context, *domain.GoodsType) (int64, error)
}

type GoodsTypeUsecase struct {
repo GoodsTypeRepo
bRepo BrandRepo
log *log.Helper
}

func NewGoodsTypeUsecase(repo GoodsTypeRepo, tx Transaction, bRepo BrandRepo, logger log.Logger) *GoodsTypeUsecase {
return &GoodsTypeUsecase{
repo: repo,
tx: tx,
bRepo: bRepo,
log: log.NewHelper(logger),
}
}

// GoosTypeCreate 创建商品类型
func (gt *GoodsTypeUsecase) GoosTypeCreate(ctx context.Context, r *domain.GoodsType) (int64, error) {
id, err := gt.repo.CreateGoodsType(ctx, r)
if err != nil {
return err
}
return id, nil
}

修改 datagoods_type.go , 加入 createGoodsType 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package data

...

// CreateGoodsType 创建基本的商品类型
func (g *goodsTypeRepo) CreateGoodsType(ctx context.Context, req *domain.GoodsType) (int64, error) {
goodsType := &GoodsType{
Name: req.Name,
TypeCode: req.TypeCode,
NameAlias: req.NameAlias,
IsVirtual: req.IsVirtual,
Desc: req.Desc,
Sort: req.Sort,
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
}
result := g.data.db.Save(goodsType)
return goodsType.ID, result.Error
}

  • 测试提交
    由于之前推荐的使用 goland ide 自带的工具进行 rpc 方法测试,很多同学反应各种问题,连接失败、协议缓冲区等错误,今天咱们推荐另一个免费的开源工具,BloomRPC ,安装比较简单,这里就不演示了,自己进去他的 GitHub 主页就能看到安装步骤,使用起来也超级简单。

打开 BloomRPC 工具,导入你的 proto 文件,它就会把你写好的 rpc 方法全部列在左侧,修改好你的 IP 和 port ,
它甚至把你每个请求的方法所需要的数据都构造好了,你只要稍作修改,就可以发起请求进行测试了。如下图启动服务之后,点击绿色的按钮,得到创建成功的 ID。

结束语

本篇只提供了一个商品类型的创建方法,其他方法没有在文章中体现,单元测试方法也没有编写,重复性的工作这里就不编写了,通过前几篇的文章,相信你可以自己完善剩余的方法。

这篇引入的 domain 层,并未做其他处理,只是接收参数之后通过 domain 下定义的结构体进行转换,你会想说那么跟之前一样,通过 biz 定义的结构体不也一样转换吗?事实确实是如此。这一篇只是带你先了解一下 domain 层,具体 domain 如何利用起来,期待下一篇文章吧。

感谢您的耐心阅读,动动手指点个赞吧。

关注我获取更新