GOLANG的GRPC入门
#go #mongodb #grpc #backend

微服务体系结构是构建可扩展和健壮应用程序的首选方法之一。它涉及将大型应用程序分解为明确定义的较小组件,执行特定的任务并使用应用程序编程接口(API)集进行通信。

通信是微服务的重要组成部分;它在让服务在较大的应用程序上下文中相互交流时起着重要的作用。协议微服务用于彼此通信的一些示例包括HTTP, grpc ,消息经纪人等。

在本文中,我们将通过使用GRPC,MongoDB和Golang构建用户管理服务来探讨什么是GRPC以及如何开始。

什么是GRPC?

GRPC是一个现代的通信框架,可以在任何环境中运行并有助于有效连接服务。它于2015年推出,并由云本机计算平台(CNCF)统治。除了有效地连接分布式系统,移动应用程序,后端的前端等方面的服务外,它还支持健康检查,负载平衡,跟踪和身份验证。

GRPC为开发人员提供了一个新的视角,可以为复杂的应用程序构建媒介,因为它可以为多种语言生成客户端和服务器绑定。以下是其一些好处:

服务定义

GRPC使用协议缓冲区作为其接口描述语言,类似于JSON,并提供了身份验证,取消,超时等功能。

轻巧和表现

GRPC定义比JSON定义小30%,比传统的REST API快5到7倍。

多个平台支持

GRPC是语言不可知论,并且具有为客户端和服务器支持的语言的自动代码生成。

可伸缩

从开发人员的环境到生产,GRPC旨在扩展数百万秒的秒请求。

入门

现在,我们了解GRPC在构建可扩展应用程序中的重要性,让我们使用GRPC,MongoDB和Golang构建用户管理服务。可以找到项目源代码here

先决条件

要完全掌握本教程中提出的概念,需要以下内容:

  • 对Golang的基本理解
  • 对协议缓冲区的基本理解
  • 协议缓冲区compiler已安装
  • MongoDB account托管数据库。 Signup 是完全免费的
  • Postman或任何GRPC测试应用程序

项目和依赖关系设置

要开始,我们需要导航到所需的目录并在我们的终端中运行下面的命令:

cargo new grpc_go && cd grpc_go

此命令创建一个名为grpc_go的Golang项目,并导航到项目目录。

接下来,我们需要通过运行以下命令来初始化一个GO模块来管理项目依赖项:

go mod init grpc_go

此命令将创建一个用于跟踪项目依赖项的go.mod文件。

我们继续使用:
安装所需的依赖项

go get google.golang.org/grpc go.mongodb.org/mongo-driver/mongo github.com/joho/godotenv google.golang.org/protobuf

google.golang.org/grpc是golang的实现。

go.mongodb.org/mongo-driver/mongo是连接到mongodb的驱动程序。

github.com/joho/godotenv是用于管理环境变量的库。

google.golang.org/protobuf是协议缓冲区的golang实现。

定义用户管理协议缓冲区和编译

要开始,我们需要定义一个协议缓冲区,以表示用户管理服务中涉及的所有操作和响应。为此,首先,我们需要在根目录中创建一个proto文件夹,在此文件夹中,创建一个user.proto文件,然后添加下面的摘要:

syntax = "proto3";
package user;
option go_package = "grpc_go/proto";

service UserService {
    rpc GetUser (UserRequest) returns (UserResponse);
    rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
    rpc UpdateUser (UpdateUserRequest) returns (UpdateUserResponse);
    rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse);
    rpc GetAllUsers (Empty) returns (GetAllUsersResponse);
}

message UserRequest {
    string id = 1;
}

message UserResponse {
    string id = 1;
    string name = 2;
    string location = 3;
    string title = 4;
}

message CreateUserRequest {
    string name = 2;
    string location = 3;
    string title = 4;
}

message CreateUserResponse {
    string data = 1;
}

message UpdateUserRequest {
    string id = 1;
    string name = 2;
    string location = 3;
    string title = 4;
}

message UpdateUserResponse {
    string data = 1;
}

message DeleteUserRequest {
    string id = 1;
}

message DeleteUserResponse {
    string data = 1;
}

message Empty {}

message GetAllUsersResponse {
    repeated UserResponse users = 1;
}

上面的摘要执行以下操作:

  • 指定使用proto3语法
  • user声明为包装名称
  • 使用go_package选项来定义软件包的导入路径以及将存储生成的代码的位置
  • 创建一个service来创建,读取,编辑和删除(crud)用户及其相应的响应(messages。

最后,我们需要使用以下命令来编译user.proto文件:

protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    proto/user.proto

上面的命令使用协议缓冲区compiler通过指定相对部分并使用user.proto文件来生成Golang服务器和客户端代码。

要避免错误,我们必须确保将golang添加到路径上。

在成功的汇编时,我们应该看到user_grpc.pb.gouser.pb.go文件添加到proto文件夹中。这些文件包含GRPC生成的服务器和客户端代码。在本文中,我们将仅使用服务器代码。

gRPC-generated files

在我们的应用程序中使用GRPC生成的代码

完成编译过程,我们可以开始使用应用程序中的生成代码。

数据库设置和集成

首先,我们需要在MongoDB上设置一个数据库和一个集合,如下所示:

database setup

我们还需要单击连接按钮并将驱动程序更改为Go

来获取数据库连接字符串。

其次,我们必须使用我们之前创建的用户密码修改复制的连接字符串并更改数据库名称。为此,我们需要在根目录中创建一个.env文件,然后添加摘要:

MONGOURI=mongodb+srv://<YOUR USERNAME HERE>:<YOUR PASSWORD HERE>@cluster0.e5akf.mongodb.net/<DATABASE NAME>?retryWrites=true&w=majority

以下正确填充的连接字符串的示例:

MONGOURI=mongodb+srv://malomz:malomzPassword@cluster0.e5akf.mongodb.net/projectMngt?retryWrites=true&w=majority

第三,我们需要创建一个辅助功能,以使用github.com/joho/godotenv库加载环境变量。为此,我们需要在根目录中创建一个configs文件夹。在这里,创建一个env.go文件,然后添加下面的摘要:

package configs

import (
    "log"
    "os"
    "github.com/joho/godotenv"
)

func EnvMongoURI() string {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    return os.Getenv("MONGOURI")
}

第四,我们需要创建一个模型来表示我们的应用程序数据。为此,我们需要在同一configs文件夹中创建user_model.go文件,然后添加下面的摘要:

package configs

import "go.mongodb.org/mongo-driver/bson/primitive"

type User struct {
    Id       primitive.ObjectID `json:"id,omitempty"`
    Name     string             `json:"name,omitempty" validate:"required"`
    Location string             `json:"location,omitempty" validate:"required"`
    Title    string             `json:"title,omitempty" validate:"required"`
}

最后,我们需要创建一个db.go文件以在同一configs文件夹中实现我们的数据库逻辑,然后添加下面的摘要:

package configs

import (
    "context"
    "fmt"
    "log"
    "time"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

type dbHandler interface {
    GetUser(id string) (*User, error)
    CreateUser(user User) (*mongo.InsertOneResult, error)
    UpdateUser(id string, user User) (*mongo.UpdateResult, error)
    DeleteUser(id string) (*mongo.DeleteResult, error)
    GetAllUsers() ([]*User, error)
}

type DB struct {
    client *mongo.Client
}

func NewDBHandler() dbHandler {
    client, err := mongo.NewClient(options.Client().ApplyURI(EnvMongoURI()))
    if err != nil {
        log.Fatal(err)
    }

    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    err = client.Connect(ctx)
    if err != nil {
        log.Fatal(err)
    }

    //ping the database
    err = client.Ping(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Connected to MongoDB")

    return &DB{client: client}
}

func colHelper(db *DB) *mongo.Collection {
    return db.client.Database("projectMngt").Collection("User")
}

func (db *DB) CreateUser(user User) (*mongo.InsertOneResult, error) {
    col := colHelper(db)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    newUser := User{
        Id:       primitive.NewObjectID(),
        Name:     user.Name,
        Location: user.Location,
        Title:    user.Title,
    }
    res, err := col.InsertOne(ctx, newUser)

    if err != nil {
        return nil, err
    }

    return res, err
}

func (db *DB) GetUser(id string) (*User, error) {
    col := colHelper(db)
    var user User
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(id)

    err := col.FindOne(ctx, bson.M{"_id": objId}).Decode(&user)

    if err != nil {
        return nil, err
    }

    return &user, err
}

func (db *DB) UpdateUser(id string, user User) (*mongo.UpdateResult, error) {
    col := colHelper(db)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(id)

    update := bson.M{"name": user.Name, "location": user.Location, "title": user.Title}
    result, err := col.UpdateOne(ctx, bson.M{"_id": objId}, bson.M{"$set": update})

    if err != nil {
        return nil, err
    }

    return result, err
}

func (db *DB) DeleteUser(id string) (*mongo.DeleteResult, error) {
    col := colHelper(db)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(id)

    result, err := col.DeleteOne(ctx, bson.M{"_id": objId})

    if err != nil {
        return nil, err
    }

    return result, err
}

func (db *DB) GetAllUsers() ([]*User, error) {
    col := colHelper(db)
    var users []*User
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    results, err := col.Find(ctx, bson.M{})

    if err != nil {
        return nil, err
    }

    for results.Next(ctx) {
        var singleUser *User
        if err = results.Decode(&singleUser); err != nil {
            return nil, err
        }
        users = append(users, singleUser)
    }

    return users, err
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 第15-21行:定义一个dbHandler接口,描述了我们的用户管理服务中所有关联的功能
  • 第23-25行:使用col属性创建DB结构,该属性将实现dbHandler接口
  • 第27-48行:创建一个NewDBHandler构造函数,该函数将DB struct和dbHandler接口绑定来通过将数据库连接初始化为MongoDB并返回适当的响应来实现,并返回适当的响应
  • 第50-52行:创建一个colHelper函数,该功能通过指定数据库名称和关联集合来接受数据库连接
  • 第54-145行:使用DB指针接收器创建所需的方法CreateUserGetUserUpdateUserUpdateUserDeleteUserGetAllUsers,并返回适当的响应。该方法还使用MongoDB的适当方法执行所需的操作

将数据库逻辑与GRPC生成的代码

集成

使用我们的数据库逻辑设置,我们可以使用这些方法来创建应用程序处理程序。为此,我们需要创建一个service文件夹;在这里,创建一个user_service.go文件,然后添加下面的摘要:

package services

import (
    "context"
    "grpc_go/configs"
    pb "grpc_go/proto"
)

var db = configs.NewDBHandler()

type UserServiceServer struct {
    pb.UnimplementedUserServiceServer
}

func (service *UserServiceServer) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
    resp, err := db.GetUser(req.Id)

    if err != nil {
        return nil, err
    }

    return &pb.UserResponse{Id: resp.Id.String(), Name: resp.Name, Location: resp.Location, Title: resp.Title}, nil
}

func (service *UserServiceServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
    newUser := configs.User{Name: req.Name, Location: req.Location, Title: req.Title}
    _, err := db.CreateUser(newUser)

    if err != nil {
        return nil, err
    }

    return &pb.CreateUserResponse{Data: "User created successfully!"}, nil
}

func (service *UserServiceServer) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) {
    newUser := configs.User{Name: req.Name, Location: req.Location, Title: req.Title}
    _, err := db.UpdateUser(req.Id, newUser)

    if err != nil {
        return nil, err
    }

    return &pb.UpdateUserResponse{Data: "User updated successfully!"}, nil
}

func (service *UserServiceServer) DeleteUser(ctx context.Context, req *pb.DeleteUserRequest) (*pb.DeleteUserResponse, error) {
    _, err := db.DeleteUser(req.Id)

    if err != nil {
        return nil, err
    }

    return &pb.DeleteUserResponse{Data: "User details deleted successfully!"}, nil
}

func (service *UserServiceServer) GetAllUsers(context.Context, *pb.Empty) (*pb.GetAllUsersResponse, error) {
    resp, err := db.GetAllUsers()
    var users []*pb.UserResponse

    if err != nil {
        return nil, err
    }

    for _, v := range resp {
        var singleUser = &pb.UserResponse{
            Id:       v.Id.String(),
            Name:     v.Name,
            Location: v.Location,
            Title:    v.Title,
        }
        users = append(users, singleUser)
    }

    return &pb.GetAllUsersResponse{Users: users}, nil
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 使用NewDBHandler构造函数函数初始化数据库
  • 创建一个UserServiceServer,该UserServiceServeruser_grpc.pb.go文件中实现GRPC生成的UserServiceServer接口
  • 通过将UserServiceServer struct作为指针传递并返回GRPC生成的适当响应来创建所需的方法

创建服务器

完成此操作,我们可以通过在根目录中创建一个main.go文件来创建应用程序GRPC服务器,然后添加下面的摘要:

package main

import (
    "log"
    "net"

    pb "grpc_go/proto"
    "grpc_go/services"
    "google.golang.org/grpc"
)
func main() {
    lis, err := net.Listen("tcp", "[::1]:8080")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    grpcServer := grpc.NewServer()
    service := &services.UserServiceServer{}

    pb.RegisterUserServiceServer(grpcServer, service)
    err = grpcServer.Serve(lis)

    if err != nil {
        log.Fatalf("Error strating server: %v", err)
    }
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 使用内置的net软件包指定应用程序端口
  • 使用NewServer方法创建GRPC服务器的实例,并使用UserServiceServer struct
  • 指定关联的服务
  • 用GRPC服务器注册服务实现
  • 通过传递所需端口并适当处理错误
  • ,使用Serve方法启动服务器

完成此操作,我们可以通过在终端中运行以下命令来测试我们的应用程序。

go run main.go

与Postman进行测试

在我们的服务器启动和运行时,我们可以通过创建新的 grpc请求

来测试我们的应用程序。

Create new

Select gRPC Request

输入grpc://[::1]:8080作为URL,选择导入一个.proto文件选项并上传我们之前创建的user.proto文件。

URL and upload proto file

完成此操作,相应的方法将被填充,我们可以相应地测试它们。

getAllUsers
getAUser

我们还可以通过检查MongoDB Collection

来验证我们的GRPC服务器的工作原理

Collection

结论

这篇文章讨论了GRPC是什么,其在构建可扩展应用程序中的作用以及如何通过使用Golang和MongoDB构建用户管理服务开始。除了上面讨论的内容之外,GRPC还提供了围绕身份验证,错误处理,性能等的强大技术

这些资源可能会有所帮助: