preface
We will show you a go zero micro service example in detail through a series of articles. The whole series is divided into ten articles, and the directory structure is as follows:
- Environment construction
- Service splitting
- User services
- Products and services (this article)
- Order service
- Payment services
- RPC service Auth authentication
- Service monitoring
- Link tracking
- Distributed transaction
This series is expected to take you to quickly develop a mall system using go zero in the Docker environment on the machine, so that you can quickly start microservices.
Complete sample code: github.com/nivin-studio/go-zero-ma...
First, let's take a look at the overall service splitting diagram:
4. Products and services
- Enter the service workspace
$ cd mall/service/product
4.1 generating a product model
- Create sql file
$ vim model/product.sql
- Writing sql files
CREATE TABLE `product` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Product name', `desc` varchar(255) NOT NULL DEFAULT '' COMMENT 'Product description', `stock` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Product inventory', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Product amount', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'Product status', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- Run template generation command
$ goctl model mysql ddl -src ./model/product.sql -dir ./model -c
4.2 generating product api services
- Create api file
$ vim api/product.api
- Writing api files
type ( // Product creation CreateRequest { Name string `json:"name"` Desc string `json:"desc"` Stock int64 `json:"stock"` Amount int64 `json:"amount"` Status int64 `json:"status"` } CreateResponse { Id int64 `json:"id"` } // Product creation // Product modification UpdateRequest { Id int64 `json:"id"` Name string `json:"name,optional"` Desc string `json:"desc,optional"` Stock int64 `json:"stock"` Amount int64 `json:"amount,optional"` Status int64 `json:"status,optional"` } UpdateResponse { } // Product modification // Product deletion RemoveRequest { Id int64 `json:"id"` } RemoveResponse { } // Product deletion // product details DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Name string `json:"name"` Desc string `json:"desc"` Stock int64 `json:"stock"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // product details ) @server( jwt: Auth ) service Product { @handler Create post /api/product/create(CreateRequest) returns (CreateResponse) @handler Update post /api/product/update(UpdateRequest) returns (UpdateResponse) @handler Remove post /api/product/remove(RemoveRequest) returns (RemoveResponse) @handler Detail post /api/product/detail(DetailRequest) returns (DetailResponse) }
- Run template generation command
$ goctl api go -api ./api/product.api -dir ./api
4.3 generate product rpc service
- Create proto file
$ vim rpc/product.proto
- Write proto file
syntax = "proto3"; package productclient; option go_package = "product"; // Product creation message CreateRequest { string Name = 1; string Desc = 2; int64 Stock = 3; int64 Amount = 4; int64 Status = 5; } message CreateResponse { int64 id = 1; } // Product creation // Product modification message UpdateRequest { int64 id = 1; string Name = 2; string Desc = 3; int64 Stock = 4; int64 Amount = 5; int64 Status = 6; } message UpdateResponse { } // Product modification // Product deletion message RemoveRequest { int64 id = 1; } message RemoveResponse { } // Product deletion // product details message DetailRequest { int64 id = 1; } message DetailResponse { int64 id = 1; string Name = 2; string Desc = 3; int64 Stock = 4; int64 Amount = 5; int64 Status = 6; } // product details service Product { rpc Create(CreateRequest) returns(CreateResponse); rpc Update(UpdateRequest) returns(UpdateResponse); rpc Remove(RemoveRequest) returns(RemoveResponse); rpc Detail(DetailRequest) returns(DetailResponse); }
- Run template generation command
$ goctl rpc proto -src ./rpc/product.proto -dir ./rpc
4.4 writing a product rpc service
4.4.1 modify configuration file
- Modify product Yaml profile
$ vim rpc/etc/product.yaml
- Modify the service listening address. The port number is 0.0.0.0:9001, Etcd service configuration, Mysql service configuration, and CacheRedis service configuration
Name: product.rpc ListenOn: 0.0.0.0:9001 Etcd: Hosts: - etcd:2379 Key: product.rpc Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node # node can not be written and can be set as cluster # Pass: xxx # If you have a password
4.4.2 adding a product model dependency
- Add Mysql service configuration and instantiate CacheRedis service configuration
$ vim rpc/internal/config/config.go
package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf }
- Register the dependency of the service context product model
$ vim rpc/internal/svc/servicecontext.go
package svc import ( "mall/service/product/model" "mall/service/product/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx" ) type ServiceContext struct { Config config.Config ProductModel model.ProductModel } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, ProductModel: model.NewProductModel(conn, c.CacheRedis), } }
4.4.3 add product creation logic Create
$ vim rpc/internal/logic/createlogic.go
package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type CreateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic { return &CreateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *CreateLogic) Create(in *product.CreateRequest) (*product.CreateResponse, error) { newProduct := model.Product{ Name: in.Name, Desc: in.Desc, Stock: in.Stock, Amount: in.Amount, Status: in.Status, } res, err := l.svcCtx.ProductModel.Insert(&newProduct) if err != nil { return nil, status.Error(500, err.Error()) } newProduct.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } return &product.CreateResponse{ Id: newProduct.Id, }, nil }
4.4.4 add product Detail logic Detail
$ vim rpc/internal/logic/detaillogic.go
package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type DetailLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic { return &DetailLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *DetailLogic) Detail(in *product.DetailRequest) (*product.DetailResponse, error) { // Query whether the product exists res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Product does not exist") } return nil, status.Error(500, err.Error()) } return &product.DetailResponse{ Id: res.Id, Name: res.Name, Desc: res.Desc, Stock: res.Stock, Amount: res.Amount, Status: res.Status, }, nil }
4.4.5 add product Update logic Update
$ vim rpc/internal/logic/updatelogic.go
package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type UpdateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogic { return &UpdateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *UpdateLogic) Update(in *product.UpdateRequest) (*product.UpdateResponse, error) { // Query whether the product exists res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Product does not exist") } return nil, status.Error(500, err.Error()) } if in.Name != "" { res.Name = in.Name } if in.Desc != "" { res.Desc = in.Desc } if in.Stock != 0 { res.Stock = in.Stock } if in.Amount != 0 { res.Amount = in.Amount } if in.Status != 0 { res.Status = in.Status } err = l.svcCtx.ProductModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } return &product.UpdateResponse{}, nil }
4.4.6 add product delete logic Remove
$ vim rpc/internal/logic/removelogic.go
package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type RemoveLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveLogic { return &RemoveLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *RemoveLogic) Remove(in *product.RemoveRequest) (*product.RemoveResponse, error) { // Query whether the product exists res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Product does not exist") } return nil, status.Error(500, err.Error()) } err = l.svcCtx.ProductModel.Delete(res.Id) if err != nil { return nil, status.Error(500, err.Error()) } return &product.RemoveResponse{}, nil }
4.5 writing product api services
4.5.1 modify configuration file
- Modify product Yaml profile
$ vim api/etc/product.yaml
- Modify the service address, the port number is 0.0.0.0:8001, the Mysql service configuration, the CacheRedis service configuration, and the Auth authentication configuration
Name: Product Host: 0.0.0.0 Port: 8001 Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node # node can not be written and can be set as cluster # Pass: xxx # If you have a password Auth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 86400
4.5.2 adding a product rpc dependency
- Add product rpc service configuration
$ vim api/etc/product.yaml
Name: Product Host: 0.0.0.0 Port: 8001 ... ProductRpc: Etcd: Hosts: - etcd:2379 Key: product.rpc
- Add instantiation of product rpc service configuration
$ vim api/internal/config/config.go
package config import ( "github.com/tal-tech/go-zero/rest" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { rest.RestConf Auth struct { AccessSecret string AccessExpire int64 } ProductRpc zrpc.RpcClientConf }
- Register the dependency of the service context product rpc
$ vim api/internal/svc/servicecontext.go
package svc import ( "mall/service/product/api/internal/config" "mall/service/product/rpc/productclient" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config ProductRpc productclient.Product } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)), } }
4.5.3 add product creation logic
$ vim api/internal/logic/createlogic.go
package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type CreateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateLogic { return CreateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *CreateLogic) Create(req types.CreateRequest) (resp *types.CreateResponse, err error) { res, err := l.svcCtx.ProductRpc.Create(l.ctx, &product.CreateRequest{ Name: req.Name, Desc: req.Desc, Stock: req.Stock, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.CreateResponse{ Id: res.Id, }, nil }
4.5.4 add product Detail logic Detail
$ vim api/internal/logic/detaillogic.go
package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type DetailLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) DetailLogic { return DetailLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *DetailLogic) Detail(req types.DetailRequest) (resp *types.DetailResponse, err error) { res, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.DetailResponse{ Id: res.Id, Name: res.Name, Desc: res.Desc, Stock: res.Stock, Amount: res.Amount, Status: res.Status, }, nil }
4.5.5 add product Update logic Update
$ vim api/internal/logic/updatelogic.go
package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type UpdateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) UpdateLogic { return UpdateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *UpdateLogic) Update(req types.UpdateRequest) (resp *types.UpdateResponse, err error) { _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{ Id: req.Id, Name: req.Name, Desc: req.Desc, Stock: req.Stock, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.UpdateResponse{}, nil }
4.5.6 add product delete logic Remove
$ vim api/internal/logic/removelogic.go
package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type RemoveLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) RemoveLogic { return RemoveLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *RemoveLogic) Remove(req types.RemoveRequest) (resp *types.RemoveResponse, err error) { _, err = l.svcCtx.ProductRpc.Remove(l.ctx, &product.RemoveRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.RemoveResponse{}, nil }
4.6 starting the product rpc service
Tip: start the service in the golang container
$ cd mall/service/product/rpc $ go run product.go -f etc/product.yaml Starting rpc server at 127.0.0.1:9001...
4.7 starting the product api service
Tip: start the service in the golang container
$ cd mall/service/product/api $ go run product.go -f etc/product.yaml Starting server at 0.0.0.0:8001...
Project address
Welcome to go zero and star support us!
Wechat communication group
Focus on the "micro service practice" official account and click on the exchange group to get the community community's two-dimensional code.