Skip to content

🔌 gRPC Framework Microservices

gRPC — Gọi hàm trên server như gọi hàm local. Transport layer chuẩn cho microservices tại Google, Netflix, và HPN.

RPC là gì?

Remote Procedure Call — Gọi hàm trên server remote như thể nó là local function.

┌─────────────────────────────────────────────────────────────────────────┐
│                    RPC CONCEPT                                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   LOCAL CALL:                                                           │
│   ────────────                                                          │
│   AuthResponse response = authService.Login(username, password);        │
│                                                                         │
│   RPC CALL (looks identical!):                                          │
│   ────────────────────────────                                          │
│   AuthResponse response = authService.Login(username, password);        │
│   // But authService is running on a different server!                  │
│                                                                         │
│   Under the hood:                                                       │
│   ┌────────────┐     HTTP/2      ┌────────────┐                         │
│   │   Client   │ ─────────────► │   Server   │                         │
│   │   Stub     │ ◄───────────── │   Handler  │                         │
│   └────────────┘   Protobuf     └────────────┘                         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

gRPC vs REST — Comparison (@[/api-design])

AspectRESTgRPC
ProtocolHTTP/1.1HTTP/2
PayloadJSON (text)Protobuf (binary)
ContractOpenAPI/Swagger (optional).proto (mandatory)
Streaming❌ (WebSocket workaround)✅ Native bidirectional
Code GenerationManual/OptionalAutomatic
Browser Support✅ Native⚠️ Requires grpc-web proxy
Latency~10ms~1ms
Throughput~10K req/s~100K req/s

🔴 HPN ENGINEERING DECISION

┌────────────────────────────────────────────────────┐
│   PUBLIC FACING APIs:     REST + JSON              │
│   INTERNAL MICROSERVICES: gRPC + Protobuf          │
│   HIGH-FREQUENCY TRADING: Custom binary protocol   │
└────────────────────────────────────────────────────┘

gRPC Communication Types

┌─────────────────────────────────────────────────────────────────────────┐
│                    gRPC STREAMING TYPES                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   1. UNARY RPC (Request-Response)                                       │
│   ───────────────────────────────                                       │
│   Client ──[Request]──► Server ──[Response]──► Client                   │
│   Use case: Login, GetUser, CreateOrder                                 │
│                                                                         │
│   2. SERVER STREAMING                                                   │
│   ───────────────────                                                   │
│   Client ──[Request]──► Server ──[Response 1]──►                        │
│                               ──[Response 2]──►                         │
│                               ──[Response N]──► Client                  │
│   Use case: Stock ticker, Log streaming                                 │
│                                                                         │
│   3. CLIENT STREAMING                                                   │
│   ───────────────────                                                   │
│   Client ──[Request 1]──►                                               │
│         ──[Request 2]──►                                                │
│         ──[Request N]──► Server ──[Response]──► Client                  │
│   Use case: File upload, Bulk import                                    │
│                                                                         │
│   4. BIDIRECTIONAL STREAMING                                            │
│   ──────────────────────────                                            │
│   Client ◄──[Messages]──► Server                                        │
│   Use case: Chat, Real-time collaboration, HPN Tunnel                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Lab: AuthService Implementation

Step 1: Define Service (.proto)

protobuf
// auth.proto
syntax = "proto3";

package hpn.auth;

// Request messages
message LoginRequest {
    string username = 1;
    string password = 2;
    optional string mfa_token = 3;
}

message ValidateTokenRequest {
    string access_token = 1;
}

// Response messages
message LoginResponse {
    enum Status {
        SUCCESS = 0;
        INVALID_CREDENTIALS = 1;
        MFA_REQUIRED = 2;
        ACCOUNT_LOCKED = 3;
    }
    
    Status status = 1;
    string access_token = 2;
    int64 expires_at = 3;
    string error_message = 4;
}

message ValidateTokenResponse {
    bool valid = 1;
    string user_id = 2;
    repeated string roles = 3;  // ["admin", "user"]
}

// Service definition
service AuthService {
    // Unary RPC
    rpc Login(LoginRequest) returns (LoginResponse);
    rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse);
    
    // Server streaming - for audit logs
    rpc StreamLoginAttempts(LoginAttemptsRequest) returns (stream LoginAttempt);
}

message LoginAttemptsRequest {
    string user_id = 1;
    int64 since_timestamp = 2;
}

message LoginAttempt {
    int64 timestamp = 1;
    string ip_address = 2;
    bool success = 3;
}

Step 2: Generate C++ Code

bash
# Generate both protobuf and gRPC code
protoc --cpp_out=generated \
       --grpc_out=generated \
       --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` \
       auth.proto

# Output files:
# generated/auth.pb.h      - Protobuf messages
# generated/auth.pb.cc
# generated/auth.grpc.pb.h - gRPC service
# generated/auth.grpc.pb.cc

Step 3: CMake Configuration

cmake
cmake_minimum_required(VERSION 3.20)
project(AuthService LANGUAGES CXX)

find_package(Protobuf REQUIRED)
find_package(gRPC CONFIG REQUIRED)

# Find grpc_cpp_plugin
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)

# Generate protobuf + grpc sources
set(PROTO_FILES ${CMAKE_SOURCE_DIR}/proto/auth.proto)

# Custom command to generate
add_custom_command(
    OUTPUT 
        ${CMAKE_BINARY_DIR}/generated/auth.pb.cc
        ${CMAKE_BINARY_DIR}/generated/auth.pb.h
        ${CMAKE_BINARY_DIR}/generated/auth.grpc.pb.cc
        ${CMAKE_BINARY_DIR}/generated/auth.grpc.pb.h
    COMMAND ${Protobuf_PROTOC_EXECUTABLE}
        --cpp_out=${CMAKE_BINARY_DIR}/generated
        --grpc_out=${CMAKE_BINARY_DIR}/generated
        --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}
        -I${CMAKE_SOURCE_DIR}/proto
        ${PROTO_FILES}
    DEPENDS ${PROTO_FILES}
)

add_library(auth_proto
    ${CMAKE_BINARY_DIR}/generated/auth.pb.cc
    ${CMAKE_BINARY_DIR}/generated/auth.grpc.pb.cc
)
target_include_directories(auth_proto PUBLIC ${CMAKE_BINARY_DIR}/generated)
target_link_libraries(auth_proto PUBLIC 
    protobuf::libprotobuf
    gRPC::grpc++
)

# Server
add_executable(auth_server src/server.cpp)
target_link_libraries(auth_server PRIVATE auth_proto)

# Client
add_executable(auth_client src/client.cpp)
target_link_libraries(auth_client PRIVATE auth_proto)

Step 4: Synchronous Server Implementation

cpp
// src/server_sync.cpp
#include <grpcpp/grpcpp.h>
#include "auth.grpc.pb.h"
#include <iostream>
#include <memory>

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using hpn::auth::AuthService;
using hpn::auth::LoginRequest;
using hpn::auth::LoginResponse;

class AuthServiceImpl final : public AuthService::Service {
public:
    Status Login(ServerContext* context, 
                 const LoginRequest* request,
                 LoginResponse* response) override {
        
        std::cout << "[LOGIN] User: " << request->username() << std::endl;
        
        // Simulate authentication (NEVER do this in production!)
        if (request->username() == "admin" && 
            request->password() == "secret") {
            
            response->set_status(LoginResponse::SUCCESS);
            response->set_access_token("jwt_token_here_" + 
                                       std::to_string(std::time(nullptr)));
            response->set_expires_at(std::time(nullptr) + 3600);
            
        } else {
            response->set_status(LoginResponse::INVALID_CREDENTIALS);
            response->set_error_message("Invalid username or password");
        }
        
        return Status::OK;
    }
};

void RunServer() {
    std::string server_address("0.0.0.0:50051");
    AuthServiceImpl service;
    
    ServerBuilder builder;
    builder.AddListeningPort(server_address, 
                             grpc::InsecureServerCredentials());
    builder.RegisterService(&service);
    
    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on " << server_address << std::endl;
    
    server->Wait();  // Block until shutdown
}

int main() {
    RunServer();
    return 0;
}

Step 5: Synchronous Client

cpp
// src/client_sync.cpp
#include <grpcpp/grpcpp.h>
#include "auth.grpc.pb.h"
#include <iostream>

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using hpn::auth::AuthService;
using hpn::auth::LoginRequest;
using hpn::auth::LoginResponse;

class AuthClient {
public:
    AuthClient(std::shared_ptr<Channel> channel)
        : stub_(AuthService::NewStub(channel)) {}
    
    std::string Login(const std::string& username, 
                      const std::string& password) {
        LoginRequest request;
        request.set_username(username);
        request.set_password(password);
        
        LoginResponse response;
        ClientContext context;
        
        // Set deadline (timeout)
        context.set_deadline(std::chrono::system_clock::now() + 
                             std::chrono::seconds(5));
        
        Status status = stub_->Login(&context, request, &response);
        
        if (status.ok()) {
            if (response.status() == LoginResponse::SUCCESS) {
                return response.access_token();
            } else {
                std::cerr << "Login failed: " 
                          << response.error_message() << std::endl;
                return "";
            }
        } else {
            std::cerr << "RPC failed: " 
                      << status.error_message() << std::endl;
            return "";
        }
    }

private:
    std::unique_ptr<AuthService::Stub> stub_;
};

int main() {
    auto channel = grpc::CreateChannel("localhost:50051", 
                                       grpc::InsecureChannelCredentials());
    
    AuthClient client(channel);
    
    std::string token = client.Login("admin", "secret");
    if (!token.empty()) {
        std::cout << "Login successful! Token: " << token << std::endl;
    }
    
    return 0;
}

Async gRPC Server (Production Grade)

Synchronous server = 1 thread per request = KHÔNG SCALE.

cpp
// src/server_async.cpp
#include <grpcpp/grpcpp.h>
#include "auth.grpc.pb.h"
#include <thread>
#include <memory>

using grpc::Server;
using grpc::ServerAsyncResponseWriter;
using grpc::ServerBuilder;
using grpc::ServerCompletionQueue;
using grpc::ServerContext;
using grpc::Status;

class AsyncAuthServer {
public:
    ~AsyncAuthServer() {
        server_->Shutdown();
        cq_->Shutdown();
    }
    
    void Run() {
        std::string server_address("0.0.0.0:50051");
        
        ServerBuilder builder;
        builder.AddListeningPort(server_address, 
                                 grpc::InsecureServerCredentials());
        builder.RegisterService(&service_);
        
        cq_ = builder.AddCompletionQueue();
        server_ = builder.BuildAndStart();
        
        std::cout << "Async server listening on " 
                  << server_address << std::endl;
        
        HandleRpcs();
    }

private:
    // CallData encapsulates the state of a single RPC
    class CallData {
    public:
        CallData(hpn::auth::AuthService::AsyncService* service,
                 ServerCompletionQueue* cq)
            : service_(service), cq_(cq), 
              responder_(&ctx_), status_(CREATE) {
            
            Proceed();
        }
        
        void Proceed() {
            switch (status_) {
                case CREATE:
                    status_ = PROCESS;
                    service_->RequestLogin(&ctx_, &request_, 
                                           &responder_, cq_, cq_, this);
                    break;
                    
                case PROCESS:
                    // Spawn new CallData to handle next request
                    new CallData(service_, cq_);
                    
                    // Process this request
                    ProcessLogin();
                    
                    status_ = FINISH;
                    responder_.Finish(response_, Status::OK, this);
                    break;
                    
                case FINISH:
                    delete this;
                    break;
            }
        }
        
    private:
        void ProcessLogin() {
            if (request_.username() == "admin" && 
                request_.password() == "secret") {
                response_.set_status(hpn::auth::LoginResponse::SUCCESS);
                response_.set_access_token("async_jwt_token");
            } else {
                response_.set_status(
                    hpn::auth::LoginResponse::INVALID_CREDENTIALS);
            }
        }
        
        hpn::auth::AuthService::AsyncService* service_;
        ServerCompletionQueue* cq_;
        ServerContext ctx_;
        
        hpn::auth::LoginRequest request_;
        hpn::auth::LoginResponse response_;
        
        ServerAsyncResponseWriter<hpn::auth::LoginResponse> responder_;
        
        enum CallStatus { CREATE, PROCESS, FINISH };
        CallStatus status_;
    };
    
    void HandleRpcs() {
        new CallData(&service_, cq_.get());
        
        void* tag;
        bool ok;
        
        while (true) {
            // Block waiting for next event
            cq_->Next(&tag, &ok);
            
            if (ok) {
                static_cast<CallData*>(tag)->Proceed();
            }
        }
    }
    
    std::unique_ptr<ServerCompletionQueue> cq_;
    hpn::auth::AuthService::AsyncService service_;
    std::unique_ptr<Server> server_;
};

int main() {
    AsyncAuthServer server;
    server.Run();
    return 0;
}

gRPC Interceptors (Middleware)

cpp
// Logging interceptor
class LoggingInterceptor : public grpc::experimental::Interceptor {
public:
    void Intercept(grpc::experimental::InterceptorBatchMethods* methods) 
        override {
        
        if (methods->QueryInterceptionHookPoint(
                grpc::experimental::InterceptionHookPoints::
                    PRE_SEND_MESSAGE)) {
            
            auto now = std::chrono::system_clock::now();
            std::cout << "[" << time_point_to_string(now) << "] "
                      << "RPC started" << std::endl;
        }
        
        methods->Proceed();
    }
};

// Factory
class LoggingInterceptorFactory 
    : public grpc::experimental::ServerInterceptorFactoryInterface {
public:
    grpc::experimental::Interceptor* CreateServerInterceptor(
        grpc::experimental::ServerRpcInfo* info) override {
        return new LoggingInterceptor();
    }
};

Best Practices

┌─────────────────────────────────────────────────────────────────────────┐
│                    gRPC BEST PRACTICES                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ✅ DO                                                                  │
│   ─────                                                                 │
│   • Always set deadlines (context.set_deadline())                       │
│   • Use async server for production workloads                           │
│   • Enable keepalive for long-lived connections                         │
│   • Use interceptors for cross-cutting concerns (logging, auth)         │
│   • Implement health checking (grpc.health.v1.Health)                   │
│                                                                         │
│   ❌ DON'T                                                               │
│   ───────                                                               │
│   • Don't use sync server in production (doesn't scale)                 │
│   • Don't forget to handle Status::CANCELLED                            │
│   • Don't send large messages (> 4MB default limit)                     │
│   • Don't ignore error codes (implement proper error handling)          │
│                                                                         │
│   ⚠️ SECURITY                                                           │
│   ────────────                                                          │
│   • NEVER use InsecureServerCredentials() in production                 │
│   • Always use TLS/SSL for transport security                           │
│   • Implement authentication (JWT, mutual TLS)                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Bước tiếp theo

Đã hiểu gRPC, giờ học engine bên dưới:

Async I/O → — Event Loop, Boost.Asio, C++20 Coroutines