kratos编写流程
正如前面的提到的,kratos是protobuf驱动的框架,使用kratos proto add
添加模板。
kratos proto add api/review/v1/review.proto
。
syntax = "proto3";
package api.review.v1;
import "google/api/annotations.proto";
option go_package = "review-service/api/review/v1;v1";
option java_multiple_files = true;
option java_package = "api.review.v1";
service Review {
rpc CreateReview (CreateReviewRequest) returns (CreateReviewReply){
option (google.api.http) ={
post: "/v1/review",
body: "*",
};
};
rpc UpdateReview (UpdateReviewRequest) returns (UpdateReviewReply);
rpc DeleteReview (DeleteReviewRequest) returns (DeleteReviewReply);
rpc GetReview (GetReviewRequest) returns (GetReviewReply);
rpc ListReview (ListReviewRequest) returns (ListReviewReply);
}
message CreateReviewRequest {
int64 userID = 1;
int64 orderID = 2;
int32 score = 3;
int32 serviceScore = 4;
int32 expressScore = 5;
string content = 6;
string picInfo = 7;
string videoInfo = 8;
bool anonymous = 9;
}
message CreateReviewReply {
int64 reviewID = 1;
}
message UpdateReviewRequest {}
message UpdateReviewReply {}
message DeleteReviewRequest {}
message DeleteReviewReply {}
message GetReviewRequest {}
message GetReviewReply {}
message ListReviewRequest {}
message ListReviewReply {}
生成客户端,服务端代码
kratos proto client api/review/v1/review.proto
kratos proto server api/review/v1/review.proto -t internal/service
服务此时通过server->service->diz->data
调用。
正如之前章节提到,此时service
处理来的请求,调用diz
层最后由data
保存到数据库。
type ReviewService struct {
pb.UnimplementedReviewServer
//依赖注入:service 层调用 biz 层的 Usecase 来执行业务逻辑
uc *biz.ReviewUsecase
}
func NewReviewService(uc *biz.ReviewUsecase) *ReviewService {
return &ReviewService{uc: uc}
}
func (s *ReviewService) CreateReview(ctx context.Context, req *pb.CreateReviewRequest) (*pb.CreateReviewReply, error) {
fmt.Printf("[service] CreateReview, req:%#v\n", req)
var anonymous int32
if req.Anonymous {
anonymous = 1
}
review, err := s.uc.CreateReview(ctx, &model.ReviewInfo{
UserID: req.UserID,
OrderID: req.OrderID,
Score: req.Score,
ServiceScore: req.Score,
ExpressScore: req.ExpressScore,
Content: req.Content,
PicInfo: req.PicInfo,
VideoInfo: req.VideoInfo,
Anonymous: anonymous,
Status: 0,
})
return &pb.CreateReviewReply{ReviewID: review.ReviewID}, err
}
func (s *ReviewService) UpdateReview(ctx context.Context, req *pb.UpdateReviewRequest) (*pb.UpdateReviewReply, error) {
return &pb.UpdateReviewReply{}, nil
}
func (s *ReviewService) DeleteReview(ctx context.Context, req *pb.DeleteReviewRequest) (*pb.DeleteReviewReply, error) {
return &pb.DeleteReviewReply{}, nil
}
func (s *ReviewService) GetReview(ctx context.Context, req *pb.GetReviewRequest) (*pb.GetReviewReply, error) {
return &pb.GetReviewReply{}, nil
}
func (s *ReviewService) ListReview(ctx context.Context, req *pb.ListReviewRequest) (*pb.ListReviewReply, error) {
return &pb.ListReviewReply{}, nil
}
实现具体逻辑的diz
层
//diz/review.go
// ReviewRepo is a ReviewRepo .
//data层即数据库应该进行的操作
type ReviewRepo interface {
SaveReview(context.Context, *model.ReviewInfo) (*model.ReviewInfo, error)
GetReviewByOrderID(context.Context, int64) ([]*model.ReviewInfo, error)
}
// ReviewUsecase is a Review usecase.
//连接data和service的结构体
type ReviewUsecase struct {
repo ReviewRepo
log *log.Helper
}
// NewGreeterUsecase new a Review usecase.
func NewReviewUsecase(repo ReviewRepo, logger log.Logger) *ReviewUsecase {
return &ReviewUsecase{repo: repo, log: log.NewHelper(logger)}
}
// CreateGreeter creates a Review, and returns the new Review.
func (uc *ReviewUsecase) CreateReview(ctx context.Context, review *model.ReviewInfo) (*model.ReviewInfo, error) {
//首先判断该订单是否已经评论过,如果评论过就返回
uc.log.WithContext(ctx).Debugf("[biz] CreateReview, req:%v", review)
reviews, err := uc.repo.GetReviewByOrderID(ctx, review.OrderID)
if err != nil {
return nil, errors.New("查询数据库失败")
}
if len(reviews) > 0 {
// 已经评价过
fmt.Printf("订单已评价, len(reviews):%d\n", len(reviews))
return nil, fmt.Errorf("订单:%d已评价", review.OrderID)
}
否则通过雪花算法生成ID
review.ReviewID = snowflake.GenID()
//保存评论
return uc.repo.SaveReview(ctx, review)
}
接着是data
type reviewRepo struct {
data *Data
log *log.Helper
}
func (r *reviewRepo) SaveReview(ctx context.Context, info *model.ReviewInfo) (*model.ReviewInfo, error) {
//TODO implement me
err := r.data.query.ReviewInfo.WithContext(ctx).Save(info)
return info, err
}
func (r *reviewRepo) GetReviewByOrderID(ctx context.Context, i int64) ([]*model.ReviewInfo, error) {
return r.data.query.ReviewInfo.
WithContext(ctx).
Where(r.data.query.ReviewInfo.OrderID.Eq(i)).
Find()
}
func NewReviewRepo(data *Data, logger log.Logger) biz.ReviewRepo {
return &reviewRepo{
data: data,
log: log.NewHelper(logger),
}
}
接下来就是补充调用配置和补充依赖注入。可以看到有mysql和雪花id都需要加载配置项,而kratos框架的config也是由protobuf生成定义的。
依次补充依赖注入。从server->service->diz->data
。
//server/server.go
var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer)
//service/service.go
// ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewReviewService)
//diz/diz.go
// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewReviewUsecase)
//data/data.go
/*不同于其他依赖注入的地方,在data层中我们需要对原代码进行修改
// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData, NewTodoRepo)
// Data .
type Data struct {
// TODO wrapped database client
// db *gorm.DB
// db sqlx.DB
}
// NewData .
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
cleanup := func() {
log.NewHelper(logger).Info("closing the data resources")
}
return &Data{}, cleanup, nil
}
*/
// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData, NewReviewRepo, NewDB)
// Data .
type Data struct {
// TODO wrapped database client
query *query.Query
log *log.Helper
}
// NewData .
//连接数据库
func NewData(db *gorm.DB, logger log.Logger) (*Data, func(), error) {
cleanup := func() {
log.NewHelper(logger).Info("closing the data resources")
}
// 非常重要!为GEN生成的query代码设置数据库连接对象
//以便我们不需要每个包都用query.useDB()
query.SetDefault(db)
return &Data{query: query.Q, log: log.NewHelper(logger)}, cleanup, nil
}
func NewDB(cfg *conf.Data) (*gorm.DB, error) {
switch strings.ToLower(cfg.Database.GetDriver()) {
case "mysql":
return gorm.Open(mysql.Open(cfg.Database.GetSource()))
case "sqlite":
return gorm.Open(sqlite.Open(cfg.Database.GetSource()))
}
return nil, errors.New("connect db fail unsupported db driver")
}
使用wire
生成
// wireApp init kratos application.
func wireApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) {
panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))
}
conf/conf.proto
syntax = "proto3";
package kratos.api;
option go_package = "review-service/internal/conf;conf";
import "google/protobuf/duration.proto";
//需要添加的有Snowflake 中的开始时间和machine_id ,其他都由模板生成。
message Bootstrap {
Server server = 1;
Data data = 2;
Snowflake snowflake = 3;
}
message Snowflake{
string start_time = 1;
int64 machine_id = 2;
}
message Server {
message HTTP {
string network = 1;
string addr = 2;
google.protobuf.Duration timeout = 3;
}
message GRPC {
string network = 1;
string addr = 2;
google.protobuf.Duration timeout = 3;
}
HTTP http = 1;
GRPC grpc = 2;
}
message Data {
message Database {
string driver = 1;
string source = 2;
}
message Redis {
string network = 1;
string addr = 2;
google.protobuf.Duration read_timeout = 3;
google.protobuf.Duration write_timeout = 4;
}
Database database = 1;
Redis redis = 2;
}
接着使用雪花算法,需要现在主函数中初始化
if err := snowflake.Init(
bc.Snowflake.StartTime,
bc.Snowflake.MachineId,
); err != nil {
panic(err)
}