Take you ten days to easily complete the Go micro service series

Posted by saranya on Thu, 20 Jan 2022 08:44:48 +0100

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:

  1. Environment construction
  2. Service splitting
  3. User services
  4. Products and services (this article)
  5. Order service
  6. Payment services
  7. RPC service Auth authentication
  8. Service monitoring
  9. Link tracking
  10. 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

github.com/zeromicro/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.

Topics: Go Framework Microservices go-zero