Mise en œuvre de go-zero : API REST et Services RPC

Ce guide présente comment utiliser l'outil goctl pour générer et structurer des services basés sur le framework go-zero, en couvrant à la fois les API REST et les services RPC.

Prérequis

Assurez-vous d'avoir installé protoc, protoc-gen-go, et goctl.

Développement d'API REST

  1. Initialisation du projet et configuration Go Modules :

    
    mkdir zeroService && cd zeroService && go mod init zeroService
    
    
  2. Gestion des versions de dépendances (exemple avec gRPC) : Ajoutez la directive replace dans go.mod pour spécifier une version de google.golang.org/grpc.

    
    module zeroService
    
    go 1.15
    
    replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
    
    
  3. Organisation des répertoires pour les API et les RPC :

    
    mkdir rpc && mkdir api
    
    
  4. Définition de l'API avec la syntaxe spécifique à go-zero (fichier api/user.api) :

    
    syntax = "v1"
    
    info(
       "user-api"
    )
    
    type (
       RegisterRequest {
           Username string `json:"username"`
           Password string `json:"password"`
           Nickname string `json:"nickname"`
           Age      int    `json:"age"`
       }
    
       RegisterResponse {
           UserID int64 `json:"userId"`
       }
    
       UserInfoRequest {
           UserID int64 `json:"userId"`
       }
    
       UserInfoResponse {
           UserID   int64  `json:"userId"`
           Username string `json:"username"`
           Nickname string `json:"nickname"`
           Age      int    `json:"age"`
       }
    )
    
    @server(
       group: user
    )
    service User {
       @doc "Enregistrement d'un nouvel utilisateur"
       @handler RegisterHandler
       post /users/register (RegisterRequest) returns (RegisterResponse)
    
       @doc "Récupération des informations utilisateur"
       @handler UserInfoHandler
       get /users/:userId (UserInfoRequest) returns (UserInfoResponse)
    }
    
    
  5. Génération du code de l'API REST avec goctl :

    
    goctl api go -api api/user.api -dir .
    
    

    Cela crée une structure de projet avec les répertoires api/etc, api/internal, etc.

  6. Configuration de l'application (fichier api/etc/user-api.yaml) :

    
    name: user-api
    host: 0.0.0.0
    port: 8888
    database:
     source: "user:password@tcp(127.0.0.1:3306)/gouserdb"
    cache:
     - host: 127.0.0.1:6379
    log:
     level: "console"
    
    

    Mettez à jour la structure de configuration dans api/internal/config/config.go pour inclure les nouvelles propriétés.

  7. Conception et génération du modèle de base de données :

    Exemple de schéma SQL pour la table users :

    
    CREATE TABLE `users` (
       `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
       `username` VARCHAR(100) NOT NULL UNIQUE,
       `nickname` VARCHAR(100) NOT NULL,
       `password` VARCHAR(255) NOT NULL,
       `age` INT DEFAULT 0,
       `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
       `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );
    
    

    Génération du modèle de données avec goctl :

    
    goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/gouserdb" -table="users" -dir="./api/model"
    
    

    Intégration du modèle dans le contexte de service (fichier api/internal/svc/servicecontext.go) :

    
    package svc
    
    import (
       "gouserdb/api/internal/config"
       "gouserdb/api/model" // Assurez-vous que le chemin est correct
    
       "github.com/zeromicro/go-zero/core/stores/sqlx"
    )
    
    type ServiceContext struct {
       Config config.Config
       UserModel model.UsersModel
    }
    
    func NewServiceContext(c config.Config) *ServiceContext {
       mysqlConn := sqlx.NewMysql(c.Database.Source)
       return &ServiceContext{
           Config: c,
           UserModel: model.NewUsersModel(mysqlConn),
       }
    }
    
    
  8. Implémentation de la logique métier pour l'API (exemple pour UserInfoHandler dans api/internal/logic/userinfologic.go) :

    
    package logic
    
    import (
    	"context"
    	"fmt"
    
    	"gouserdb/api/internal/svc"
    	"gouserdb/api/internal/types"
    
    	"github.com/zeromicro/go-zero/core/logx"
    )
    
    type UserInfoLogic struct {
    	logx.Logger
    	ctx    context.Context
    	svcCtx *svc.ServiceContext
    }
    
    func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserInfoLogic {
    	return &UserInfoLogic{
    		Logger: logx.WithContext(ctx),
    		ctx:    ctx,
    		svcCtx: svcCtx,
    	}
    }
    
    func (l *UserInfoLogic) GetUserInfo(req *types.UserInfoRequest) (*types.UserInfoResponse, error) {
    	user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.UserID)
    	if err != nil {
           if err == model.ErrNotFound {
               l.Logger.Errorf("User not found: %d", req.UserID)
               return nil, fmt.Errorf("user not found")
           }
    		l.Logger.Errorf("Error fetching user: %v", err)
    		return nil, err
    	}
    
    	return &types.UserInfoResponse{
    		UserID:   user.ID,
    		Username: user.Username,
    		Nickname: user.Nickname,
    		Age:      int(user.Age),
    	}, nil
    }
    
    
  9. Démarrage du service API :

    
    go run api/user.go
    
    

    Tester l'API avec curl.

Développement de Services RPC

  1. Définition du contrat de service RPC avec Protocol Buffers (fichier rpc/user_service.proto) :

    
    syntax = "proto3";
    
    package userService;
    
    option go_package = "./userService";
    
    message UserRegisterRequest {
       string username = 1;
       string nickname = 2;
       string password = 3;
       int64  age = 4;
    }
    
    message UserRegisterResponse {
       int64 userId = 1;
    }
    
    message GetUserRequest {
       int64 userId = 1;
    }
    
    message GetUserResponse {
       int64 userId = 1;
       string username = 2;
       string nickname = 3;
       int64  age = 4;
    }
    
    service UserService {
       rpc Register (UserRegisterRequest) returns (UserRegisterResponse);
       rpc GetUser (GetUserRequest) returns (GetUserResponse);
    }
    
    
  2. Génération du code RPC à partir du fichier .proto :

    
    goctl rpc proto -src rpc/user_service.proto -dir .
    
    

    Cela génère les fichiers dans rpc/userService et les fichiers de configuration dans rpc/etc.

  3. Configuration du service RPC (fichier rpc/etc/user_service.yaml) :

    
    name: user-rpc-service
    listenOn: 0.0.0.0:9000
    etcd:
     hosts:
     - 127.0.0.1:2379
     key: user-rpc-service
    database:
     source: "user:password@tcp(127.0.0.1:3306)/gouserdb"
    cache:
     - host: 127.0.0.1:6379
    log:
     level: "console"
    
    

    Mettez à jour la configuration dans rpc/internal/config/config.go.

  4. Intégration du modèle de données dans le contexte du service RPC (fichier rpc/internal/svc/servicecontext.go) :

    
    package svc
    
    import (
       "gouserdb/rpc/internal/config"
       "gouserdb/rpc/model" // Assurez-vous que le chemin est correct
    
       "github.com/zeromicro/go-zero/core/stores/sqlx"
       "github.com/zeromicro/go-zero/zrpc"
    )
    
    type ServiceContext struct {
       Config config.Config
       UserModel model.UsersModel
    }
    
    func NewServiceContext(c config.Config) *ServiceContext {
       mysqlConn := sqlx.NewMysql(c.Database.Source)
       return &ServiceContext{
           Config: c,
           UserModel: model.NewUsersModel(mysqlConn),
       }
    }
    
    
  5. Implémentation de la logique métier pour le service RPC (fichier rpc/internal/logic/registerlogic.go) :

    
    package logic
    
    import (
    	"context"
    	"time"
    
    	"gouserdb/rpc/internal/svc"
    	"gouserdb/rpc/userService"
    	"gouserdb/rpc/model"
    
    	"github.com/zeromicro/go-zero/core/logx"
    )
    
    type RegisterLogic struct {
    	logx.Logger
    	ctx    context.Context
    	svcCtx *svc.ServiceContext
    }
    
    func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
    	return &RegisterLogic{
    		Logger: logx.WithContext(ctx),
    		ctx:    ctx,
    		svcCtx: svcCtx,
    	}
    }
    
    func (l *RegisterLogic) RegisterUser(in *userService.UserRegisterRequest) (*userService.UserRegisterResponse, error) {
    	newUser := model.Users{
    		Username: in.Username,
    		Nickname: in.Nickname,
    		Password: in.Password,
    		Age:      in.Age,
    		// created_at et updated_at seront gérés par la base de données ou par défaut
    	}
    
    	result, err := l.svcCtx.UserModel.Insert(l.ctx, newUser)
    	if err != nil {
    		l.Logger.Errorf("Failed to insert user: %v", err)
    		return nil, err
    	}
    
    	userId, err := result.LastInsertId()
    	if err != nil {
    		l.Logger.Errorf("Failed to get last insert ID: %v", err)
    		return nil, err
    	}
    
    	return &userService.UserRegisterResponse{UserId: userId}, nil
    }
    
    
  6. Configuration de l'API pour utiliser le client RPC :

    Ajoutez la configuration du client RPC dans api/etc/user-api.yaml sous la section rpcClient.

    Mettez à jour api/internal/config/config.go pour inclure la nouvelle structure de configuration du client RPC.

    Dans api/internal/svc/servicecontext.go, ajoutez l'instance du client RPC et initialisez-la dans NewServiceContext.

  7. Implémentation de la logique de l'API pour appeler le service RPC (dans api/internal/logic/registerlogic.go) :

    
    package logic
    
    import (
    	"context"
    	"fmt"
    
    	"gouserdb/api/internal/svc"
    	"gouserdb/api/internal/types"
    	userpb "gouserdb/rpc/userService" // Alias pour éviter les conflits
    
    	"github.com/zeromicro/go-zero/core/logx"
    	"github.com/zeromicro/go-zero/zrpc"
    )
    
    type RegisterLogic struct {
    	logx.Logger
    	ctx    context.Context
    	svcCtx *svc.ServiceContext
    }
    
    func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
    	return &RegisterLogic{
    		Logger: logx.WithContext(ctx),
    		ctx:    ctx,
    		svcCtx: svcCtx,
    	}
    }
    
    func (l *RegisterLogic) Register(req types.RegisterRequest) (*types.RegisterResponse, error) {
    	// Créer la requête pour le service RPC
    	rpcReq := &userpb.UserRegisterRequest{
    		Username: req.Username,
    		Nickname: req.Nickname,
    		Password: req.Password,
    		Age:      int64(req.Age),
    	}
    
    	// Appeler le service RPC
    	rpcResp, err := l.svcCtx.UserServiceRpcClient.Register(l.ctx, rpcReq)
    	if err != nil {
    		l.Logger.Errorf("RPC call to Register failed: %v", err)
    		return nil, fmt.Errorf("failed to register user via RPC")
    	}
    
    	// Mapper la réponse RPC vers la réponse de l'API
    	return &types.RegisterResponse{UserID: int(rpcResp.UserId)}, nil
    }
    
    
  8. Démarrage des services API et RPC :

    
    # Démarrer le service RPC en arrière-plan
    go run rpc/user_service.go &
    
    # Démarrer le service API en arrière-plan
    go run api/user.go &
    
    

    Tester les points de terminaison de l'API.

Déploiement avec Docker

Utilisez goctl docker pour générer des Dockerfile pour vos services.


goctl docker -go rpc/user_service.go -port 9000
goctl docker -go api/user.go -port 8888
   

Construisez les images Docker et lancez les conteneurs.

Résumé

  • goctl est un outil puissant pour générer rapidement du code boilerplate.
  • Une attention particulière doit être portée à la gestion des dépendances et aux configurations spécifiuqes.
  • La génération de modèles avec support de cache automatique est une fonctionnalité notable pour optimiser les accès à la base de données.

Étiquettes: go-zero goctl API REST RPC grpc

Publié le 4 juin à 17h07