Giao diện
🔌 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])
| Aspect | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 | HTTP/2 |
| Payload | JSON (text) | Protobuf (binary) |
| Contract | OpenAPI/Swagger (optional) | .proto (mandatory) |
| Streaming | ❌ (WebSocket workaround) | ✅ Native bidirectional |
| Code Generation | Manual/Optional | Automatic |
| 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.ccStep 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) │
│ │
└─────────────────────────────────────────────────────────────────────────┘