0

Bài 4: Xây dựng ứng dụng Microservices đầu tiên với gRPC (Coding Demo).

Để dễ hình dung và trực quan nhất, chúng ta sẽ xây dựng một ví dụ thực tế: Quản lý người dùng (User Service) sử dụng ngôn ngữ Go (Golang) – một trong những ngôn ngữ hỗ trợ gRPC mạnh mẽ và phổ biến nhất hiện nay.

🛠️ Các bước thực hiện

  1. Thiết lập cấu trúc Project và cài đặt công cụ.

  2. Viết file định nghĩa .proto.

  3. Sinh mã nguồn tự động (Code Generation).

  4. Viết mã nguồn phía Server.

  5. Viết mã nguồn phía Client để gọi thử.

Bước 1: Khởi tạo Project

Tạo một thư mục mới cho project của bạn và khởi tạo module Go:

Bash

mkdir grpc-demo
cd grpc-demo
go mod init grpc-demo

Bạn cần cài đặt các công cụ biên dịch Protobuf và thư viện gRPC cho Go:

Bash

# Cài đặt thư viện gRPC cho Go
google.golang.org/grpc
google.golang.org/protobuf/cmd/protoc-gen-go
google.golang.org/grpc/cmd/protoc-gen-go-grpc

(Lưu ý: Máy tính của bạn cũng cần cài sẵn công cụ biên dịch protoc của Google).

Bước 2: Viết file định nghĩa .proto

Tạo một thư mục tên là proto và tạo file user.proto:

Protocol Buffers

syntax = "proto3";

package user;

option go_package = "grpc-demo/proto;userpb";

// Định nghĩa Service
service UserService {
    // Unary RPC: Lấy thông tin user theo ID
    rpc GetUser (UserRequest) returns (UserResponse);
}

// Request gửi lên
message UserRequest {
    int32 id = 1;
}

// Response trả về
message UserResponse {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

Bước 3: Sinh mã nguồn tự động

Chạy lệnh sau trong terminal để protoc tự động sinh ra các file code Go từ file user.proto:

Bash

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

Sau lệnh này, bạn sẽ thấy các file user.pb.gouser_grpc.pb.go xuất hiện trong thư mục proto/. Đây là các file chứa code kết nối mà bạn không cần phải tự viết thủ công.

Bước 4: Viết mã nguồn phía Server

Tạo file server.go. Đây là nơi lắng nghe các request từ client và xử lý logic nghiệp vụ:

Go

package main

import (
	"context"
	"fmt"
	"log"
	"net"

	userpb "grpc-demo/proto"

	"google.golang.org/grpc"
)

// 1. Khai báo struct Server và implement interface do gRPC sinh ra
type server struct {
	userpb.UnimplementedUserServiceServer
}

// 2. Viết hàm xử lý logic cho GetUser
func (s *server) GetUser(ctx context.Context, req *userpb.UserRequest) (*userpb.UserResponse, error) {
	log.Printf("Nhận request lấy thông tin user có ID: %d", req.GetId())

	// Giả lập dữ liệu trả về từ Database
	return &userpb.UserResponse{
		Id:    req.GetId(),
		Name:  "Nguyễn Văn A",
		Email: "nguyenvana@example.com",
	}, nil
}

func main() {
	// Khởi động TCP listener ở cổng 50051
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("Không thể lắng nghe cổng 50051: %v", err)
	}

	// Tạo một gRPC server mới
	grpcServer := grpc.NewServer()

	// Đăng ký UserService với gRPC server
	userpb.RegisterUserServiceServer(grpcServer, &server{})

	fmt.Println("🚀 gRPC Server đang chạy trên cổng 50051...")
	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("Lỗi khi chạy server: %v", err)
	}
}

Bước 5: Viết mã nguồn phía Client

Tạo file client.go. Đây là nơi gọi hàm từ Server một cách mượt mà hệt như gọi hàm local:

Go

package main

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

	userpb "grpc-demo/proto"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// Kết nối tới gRPC Server (không dùng bảo mật TLS trong demo này)
	conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("Không thể kết nối đến server: %v", err)
	}
	defer conn.Close()

	// Tạo client stub
	client := userpb.NewUserServiceClient(conn)

	// Tạo request với ID = 99
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	req := &userpb.UserRequest{Id: 99}

	// Gọi hàm GetUser trực tiếp như một hàm bình thường!
	res, err := client.GetUser(ctx, req)
	if err != nil {
		log.Fatalf("Lỗi khi gọi GetUser: %v", err)
	}

	// In kết quả nhận được
	fmt.Printf("✅ Nhận phản hồi từ Server:\n")
	fmt.Printf("   - ID: %d\n", res.GetId())
	fmt.Printf("   - Tên: %s\n", res.GetName())
	fmt.Printf("   - Email: %s\n", res.GetEmail())
}

Bước 6: Chạy thử nghiệm

Mở 2 cửa sổ terminal:

  • Terminal 1 (Chạy Server):

    Bash

    go run server.go
    
    
  • Terminal 2 (Chạy Client):

    Bash

    go run client.go
    
    

Kết quả ở Terminal Client: Bạn sẽ nhận được thông tin trả về từ Server một cách chớp nhoáng với dữ liệu đã được định hình cấu trúc rõ ràng qua Protobuf.

💡 Tóm tắt Bài 4: Chỉ với vài bước cấu hình file .proto, chạy lệnh protoc sinh code, và viết một vài dòng Go code đơn giản, bạn đã dựng lên một kiến trúc Client-Server giao tiếp bằng gRPC cực kỳ mạnh mẽ và chuẩn xác.

Bạn thấy ví dụ coding này thế nào? Ở Bài 5, chúng ta sẽ nâng cấp ứng dụng này lên tầm production với Bảo mật, Xử lý lỗi (Error Handling) và Interceptors nhé! Bạn đã sẵn sàng chưa?


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí