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
- Product service
- Order service (this article)
- 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: https://github.com/nivin-studio/go-zero-mall
First, let's take a look at the overall service splitting diagram:
5 order service
- Enter the service workspace
$ cd mall/service/order
5.1 generate order model
- Create sql file
$ vim model/order.sql
- Writing sql files
CREATE TABLE `order` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'user ID', `pid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'product ID', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Order amount', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'Order status', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_uid` (`uid`), KEY `idx_pid` (`pid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- Run template generation command
$ goctl model mysql ddl -src ./model/order.sql -dir ./model -c
5.2 generate order api service
- Create api file
$ vim api/order.api
- Writing api files
type ( // Order creation CreateRequest { Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } CreateResponse { Id int64 `json:"id"` } // Order creation // Order modification UpdateRequest { Id int64 `json:"id"` Uid int64 `json:"uid,optional"` Pid int64 `json:"pid,optional"` Amount int64 `json:"amount,optional"` Status int64 `json:"status,optional"` } UpdateResponse { } // Order modification // Order deletion RemoveRequest { Id int64 `json:"id"` } RemoveResponse { } // Order deletion // Order details DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // Order details // Order list ListRequest { Uid int64 `json:"uid"` } ListResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // Order list ) @server( jwt: Auth ) service Order { @handler Create post /api/order/create(CreateRequest) returns (CreateResponse) @handler Update post /api/order/update(UpdateRequest) returns (UpdateResponse) @handler Remove post /api/order/remove(RemoveRequest) returns (RemoveResponse) @handler Detail post /api/order/detail(DetailRequest) returns (DetailResponse) @handler List post /api/order/list(ListRequest) returns (ListResponse) }
- Run template generation command
$ goctl api go -api ./api/order.api -dir ./api
5.3 generate order rpc service
- Create proto file
$ vim rpc/order.proto
- Write proto file
syntax = "proto3"; package orderclient; option go_package = "order"; // Order creation message CreateRequest { int64 Uid = 1; int64 Pid = 2; int64 Amount = 3; int64 Status = 4; } message CreateResponse { int64 id = 1; } // Order creation // Order modification message UpdateRequest { int64 id = 1; int64 Uid = 2; int64 Pid = 3; int64 Amount = 4; int64 Status = 5; } message UpdateResponse { } // Order modification // Order deletion message RemoveRequest { int64 id = 1; } message RemoveResponse { } // Order deletion // Order details message DetailRequest { int64 id = 1; } message DetailResponse { int64 id = 1; int64 Uid = 2; int64 Pid = 3; int64 Amount = 4; int64 Status = 5; } // Order details // Order list message ListRequest { int64 uid = 1; } message ListResponse { repeated DetailResponse data = 1; } // Order list // Order payment message PaidRequest { int64 id = 1; } message PaidResponse { } // Order payment service Order { rpc Create(CreateRequest) returns(CreateResponse); rpc Update(UpdateRequest) returns(UpdateResponse); rpc Remove(RemoveRequest) returns(RemoveResponse); rpc Detail(DetailRequest) returns(DetailResponse); rpc List(ListRequest) returns(ListResponse); rpc Paid(PaidRequest) returns(PaidResponse); }
- Run template generation command
$ goctl rpc proto -src ./rpc/order.proto -dir ./rpc
5.4 writing order rpc service
5.4.1 modify configuration file
- Modify order Yaml profile
$ vim rpc/etc/order.yaml
- Modify the service listening address. The port number is 0.0.0.0:9002, Etcd service configuration, Mysql service configuration, and CacheRedis service configuration
Name: order.rpc ListenOn: 0.0.0.0:9002 Etcd: Hosts: - etcd:2379 Key: order.rpc Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node Pass:
5.4.2 add order 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 order model
$ vim rpc/internal/svc/servicecontext.go
package svc import ( "mall/service/order/model" "mall/service/order/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx" ) type ServiceContext struct { Config config.Config OrderModel model.OrderModel } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, OrderModel: model.NewOrderModel(conn, c.CacheRedis), } }
5.4.3 add user rpc and product rpc dependencies
- Add user rpc, product rpc service configuration
$ vim rpc/etc/order.yaml
Name: order.rpc ListenOn: 0.0.0.0:9002 Etcd: Hosts: - etcd:2379 Key: order.rpc ...... UserRpc: Etcd: Hosts: - etcd:2379 Key: user.rpc ProductRpc: Etcd: Hosts: - etcd:2379 Key: product.rpc
- Add user RPC and instantiate the product RPC 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 UserRpc zrpc.RpcClientConf ProductRpc zrpc.RpcClientConf }
- Register the dependency of the service context user RPC and product RPC
$ vim rpc/internal/svc/servicecontext.go
package svc import ( "mall/service/order/model" "mall/service/order/rpc/internal/config" "mall/service/product/rpc/productclient" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config OrderModel model.OrderModel UserRpc userclient.User ProductRpc productclient.Product } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, OrderModel: model.NewOrderModel(conn, c.CacheRedis), UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)), } }
5.4.4 add order creation logic Create
In the order creation process, verify whether the user exists by calling the user rpc service query, and then verify whether the product exists by calling the product rpc service query, and judge whether the product inventory is sufficient. After verification, create a user order and update the product inventory by calling the product rpc service.
$ vim rpc/internal/logic/createlogic.go
package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "mall/service/product/rpc/product" "mall/service/user/rpc/user" "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 *order.CreateRequest) (*order.CreateResponse, error) { // Query whether the user exists _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // Query whether the product exists productRes, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{ Id: in.Pid, }) if err != nil { return nil, err } // Judge whether the product inventory is sufficient if productRes.Stock <= 0 { return nil, status.Error(500, "Insufficient product inventory") } newOrder := model.Order{ Uid: in.Uid, Pid: in.Pid, Amount: in.Amount, Status: 0, } // Create order res, err := l.svcCtx.OrderModel.Insert(&newOrder) if err != nil { return nil, status.Error(500, err.Error()) } newOrder.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } // Update product inventory _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{ Id: productRes.Id, Name: productRes.Name, Desc: productRes.Desc, Stock: productRes.Stock - 1, Amount: productRes.Amount, Status: productRes.Status, }) if err != nil { return nil, err } return &order.CreateResponse{ Id: newOrder.Id, }, nil }
Note: there is a problem of data consistency in product inventory update here. In previous projects, we will use database transactions to carry out this series of operations to ensure data consistency. However, because we divide "orders" and "products" into different micro services, they may have different databases in actual projects, so we should consider ensuring data consistency in the case of Cross services, which involves the use of distributed transactions, In later chapters, we will introduce the logic of using distributed transactions to modify this order.
5.4.5 add order Detail logic Detail
$ vim rpc/internal/logic/detaillogic.go
package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "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 *order.DetailRequest) (*order.DetailResponse, error) { // Query whether the order exists res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Order does not exist") } return nil, status.Error(500, err.Error()) } return &order.DetailResponse{ Id: res.Id, Uid: res.Uid, Pid: res.Pid, Amount: res.Amount, Status: res.Status, }, nil }
5.4.6 add order Update logic Update
$ vim rpc/internal/logic/updatelogic.go
package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "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 *order.UpdateRequest) (*order.UpdateResponse, error) { // Query whether the order exists res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Order does not exist") } return nil, status.Error(500, err.Error()) } if in.Uid != 0 { res.Uid = in.Uid } if in.Pid != 0 { res.Pid = in.Pid } if in.Amount != 0 { res.Amount = in.Amount } if in.Status != 0 { res.Status = in.Status } err = l.svcCtx.OrderModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } return &order.UpdateResponse{}, nil }
5.4.7 add order delete logic Remove
$ vim rpc/internal/logic/removelogic.go
package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "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 *order.RemoveRequest) (*order.RemoveResponse, error) { // Query whether the order exists res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Order does not exist") } return nil, status.Error(500, err.Error()) } err = l.svcCtx.OrderModel.Delete(res.Id) if err != nil { return nil, status.Error(500, err.Error()) } return &order.RemoveResponse{}, nil }
5.4.8 add order List logic List
- Add the OrderModel method FindAllByUid to query all orders of the user according to uid
$ vim model/ordermodel.go
package model ...... type ( OrderModel interface { Insert(data *Order) (sql.Result, error) FindOne(id int64) (*Order, error) FindAllByUid(uid int64) ([]*Order, error) Update(data *Order) error Delete(id int64) error } ...... ) ...... func (m *defaultOrderModel) FindAllByUid(uid int64) ([]*Order, error) { var resp []*Order query := fmt.Sprintf("select %s from %s where `uid` = ?", orderRows, m.table) err := m.QueryRowsNoCache(&resp, query, uid) switch err { case nil: return resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err } } ......
- Add order list logic
$ vim rpc/internal/logic/listlogic.go
package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type ListLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListLogic { return &ListLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *ListLogic) List(in *order.ListRequest) (*order.ListResponse, error) { // Query whether the user exists _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // Query whether the order exists list, err := l.svcCtx.OrderModel.FindAllByUid(in.Uid) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Order does not exist") } return nil, status.Error(500, err.Error()) } orderList := make([]*order.DetailResponse, 0) for _, item := range list { orderList = append(orderList, &order.DetailResponse{ Id: item.Id, Uid: item.Uid, Pid: item.Pid, Amount: item.Amount, Status: item.Status, }) } return &order.ListResponse{ Data: orderList, }, nil }
5.4.9 add order payment logic Paid
$ vim rpc/internal/logic/paidlogic.go
package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type PaidLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewPaidLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaidLogic { return &PaidLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *PaidLogic) Paid(in *order.PaidRequest) (*order.PaidResponse, error) { // Query whether the order exists res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Order does not exist") } return nil, status.Error(500, err.Error()) } res.Status = 1 err = l.svcCtx.OrderModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } return &order.PaidResponse{}, nil }
5.5 writing order api service
5.5.1 modify configuration file
- Modify order Yaml profile
$ vim api/etc/order.yaml
- Modify the service address, the port number is 0.0.0.0:8002, the Mysql service configuration, the CacheRedis service configuration, and the Auth authentication configuration
Name: Order Host: 0.0.0.0 Port: 8002 Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node Pass: Auth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 86400
5.5.2 add order rpc dependency
- Add order rpc service configuration
$ vim api/etc/order.yaml
Name: Order Host: 0.0.0.0 Port: 8002 ...... OrderRpc: Etcd: Hosts: - etcd:2379 Key: order.rpc
- Add instantiation of order 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 } OrderRpc zrpc.RpcClientConf }
- Register service context user rpc dependency
$ vim api/internal/svc/servicecontext.go
package svc import ( "mall/service/order/api/internal/config" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config OrderRpc orderclient.Order } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, OrderRpc: orderclient.NewOrder(zrpc.MustNewClient(c.OrderRpc)), } }
5.5.3 add order creation logic
$ vim api/internal/logic/createlogic.go
package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "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.OrderRpc.Create(l.ctx, &orderclient.CreateRequest{ Uid: req.Uid, Pid: req.Pid, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.CreateResponse{ Id: res.Id, }, nil }
5.5.4 add order Detail logic Detail
$ vim api/internal/logic/detaillogic.go
package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "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.OrderRpc.Detail(l.ctx, &orderclient.DetailRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.DetailResponse{ Id: res.Id, Uid: res.Uid, Pid: res.Pid, Amount: res.Amount, Status: res.Status, }, nil }
5.5.5 add order Update logic Update
$ vim api/internal/logic/updatelogic.go
package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "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.OrderRpc.Update(l.ctx, &orderclient.UpdateRequest{ Id: req.Id, Uid: req.Uid, Pid: req.Pid, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.UpdateResponse{}, nil }
5.5.6 add order delete logic Remove
$ vim api/internal/logic/removelogic.go
package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "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.OrderRpc.Remove(l.ctx, &orderclient.RemoveRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.RemoveResponse{}, nil }
5.5.7 add order List logic List
$ vim api/internal/logic/listlogic.go
package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/core/logx" ) type ListLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewListLogic(ctx context.Context, svcCtx *svc.ServiceContext) ListLogic { return ListLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *ListLogic) List(req types.ListRequest) (resp []*types.ListResponse, err error) { res, err := l.svcCtx.OrderRpc.List(l.ctx, &orderclient.ListRequest{ Uid: req.Uid, }) if err != nil { return nil, err } orderList := make([]*types.ListResponse, 0) for _, item := range res.Data { orderList = append(orderList, &types.ListResponse{ Id: item.Id, Uid: item.Uid, Pid: item.Pid, Amount: item.Amount, Status: item.Status, }) } return orderList, nil }
5.6 start order rpc service
Tip: start the service in the golang container
$ cd mall/service/order/rpc $ go run order.go -f etc/order.yaml Starting rpc server at 127.0.0.1:9002...
5.7 start order api service
Tip: start the service in the golang container
$ cd mall/service/order/api $ go run order.go -f etc/order.yaml Starting server at 0.0.0.0:8002...
Project address
https://github.com/zeromicro/go-zero
https://gitee.com/kevwan/go-zero
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.