# Google remote procedure call - Giao thức giao tiếp client-server

## I. Code generation từ .proto file (Java gRPC plugin)

Trong gRPC, file `.proto` đóng vai trò như **hợp đồng (contract)** giữa client và server.

### 1. `proto` là gì?

Đây là nơi định nghĩa:

- **Message** (cấu trúc dữ liệu request/response)
- **Service** (các API)

Ví dụ:

```protobuf
service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
}
```

### 2. Code generation hoạt động thế nào?

Thay vì viết code thủ công, gRPC sử dụng `protoc` để generate:

- **Model class** (POJO từ message)
- **Stub** (client gọi server)
- **Base server class** (để implement logic)

Trong java ta dùng các thư viện để gen code từ proto

- `protobuf-maven-plugin`  – để chạy `protoc`
- `protoc-gen-grpc-java` – để generate gRPC stub

Còn trong spring thì ta dùng thêm `net.devh:grpc-spring-boot-starter` để tích hợp gRPC với Spring Boot, giúp:

- Auto config server/client
- Inject stub như Spring Bean
- Dễ dàng dùng interceptor, security

Cụ thể ta có thể cấu hình plugin maven như sau:

```xml
<properties>
    <java.version>17</java.version>
    <grpc.version>1.61.0</grpc.version>
    <protobuf.version>3.25.3</protobuf.version>
</properties>

<dependencies>
    <!-- gRPC Spring Boot Starter -->
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-spring-boot-starter</artifactId>
        <version>2.15.0.RELEASE</version>
    </dependency>

    <!-- Protobuf -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>${protobuf.version}</version>
    </dependency>

    <!-- gRPC -->
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>${grpc.version}</version>
    </dependency>

    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>${grpc.version}</version>
    </dependency>
</dependencies>

<build>
    <extensions>
        <!-- Detect OS để tải đúng binary protoc -->
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.7.1</version>
        </extension>
    </extensions>

    <plugins>
        <!-- Plugin generate code từ proto -->
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>

            <configuration>
                <protocArtifact>
                    com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
                </protocArtifact>

                <pluginId>grpc-java</pluginId>

                <pluginArtifact>
                    io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
                </pluginArtifact>
            </configuration>

            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
```

Ví dụ: với proto sau:

```protobuf
syntax = "proto3";

package auth;

option java_multiple_files = true;
option java_package = "com.aqbtech.common.proto.auth";

service AuthService {
  rpc Login (LoginRequest) returns (LoginResponse);
}

message LoginRequest {
  string username = 1;
  string password = 2;
}

message LoginResponse {
  string access_token = 1;
  string refresh_token = 2;
}
```

Sau khi chạy `protoc`, ta sẽ có:

- `LoginRequest`, `LoginResponse` class đại diện cho dữ liệu (immutable, builder pattern)
- `AuthServiceGrpc`chứa:
    - `AuthServiceImplBase` (server implement)
    - `AuthServiceBlockingStub`
    - `AuthServiceStub`
    - `AuthServiceFutureStub`

Ta có 1 service implement như sau:

```java
public class AuthServiceImpl extends AuthServiceGrpc.AuthServiceImplBase {

    @Override
    public void login(LoginRequest request,
                      StreamObserver<LoginResponse> responseObserver) {

        LoginResponse response = LoginResponse.newBuilder()
                .setAccessToken("token")
                .setRefreshToken("refresh")
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}
```

Trong đó `StreamObserver` là một interface trong gRPC Java, dùng để **nhận hoặc gửi dữ liệu theo dạng stream** giữa client và server.

Interface này có 3 phương thức chính:

```java
publicinterfaceStreamObserver<V> {
void onNext(V value);
void onError(Throwable t);
void onCompleted();
}
```

- `onNext(V value)`Gửi hoặc nhận **một message**
- `onError(Throwable t)`Báo lỗi và kết thúc stream
- `onCompleted()`Kết thúc stream thành công

### 3. Các loại stub trong Java

- **Blocking stub**: gọi đồng bộ (dễ dùng, nhưng block thread)
- **Async stub**: bất đồng bộ (non-blocking, dùng callback)
- **Future stub**: trả về `ListenableFuture`

#### 3.1. Blocking sub

Blocking stub là stub gọi **đồng bộ (synchronous)**, nghĩa là:

- Thread sẽ bị block cho đến khi nhận được response
- Code viết đơn giản, giống gọi function bình thường

Ví dụ:

```java
LoginResponse response = blockingStub.login(request);
```

Đặc điểm: Không phù hợp với: High throughput system, UI thread

#### 3.2. Async stub

Async stub là stub gọi **bất đồng bộ (non-blocking)**:

- Không block thread
- Nhận kết quả thông qua callback (`StreamObserver`)

Callback là một cơ chế trong lập trình bất đồng bộ, trong đó:

- Một **hàm (function/method)** được truyền vào như một tham số
- Hàm này sẽ được **gọi lại (call back)** khi một sự kiện xảy ra (ví dụ: nhận được response, xảy ra lỗi, hoàn tất xử lý)

Trong ngữ cảnh gRPC Async stub, khi gọi async:

```java
asyncStub.login(request, new StreamObserver<LoginResponse>() {
    @Override
    public void onNext(LoginResponse value) {
        // xử lý khi nhận được response
    }

    @Override
    public void onError(Throwable t) {
        // xử lý khi có lỗi
    }

    @Override
    public void onCompleted() {
        // xử lý khi kết thúc
    }
});
```

Thì `StreamObserver` chính là **callback object**.

Cách hoạt động

1. Client gửi request
2. Không chờ response (non-blocking)
3. Khi server trả dữ liệu gRPC runtime sẽ gọi `onNext()`
4. Nếu có lỗi thì gọi `onError()`
5. Khi hoàn tất thì gọi `onCompleted()`

Đặc điểm của callback

- Không chạy ngay lập tức
- Được thực thi khi có kết quả trả về
- Thường được dùng trong:
    - I/O (network, file)
    - API bất đồng bộ
    - Event-driven system

#### 3.3. Future stub

Future stub là dạng trung gian giữa blocking và async:

- Trả về `ListenableFuture`
- Có thể:
    - block (`get()`)
    - hoặc xử lý async

Ví dụ:

```java
ListenableFuture<LoginResponse> future = futureStub.login(request);

LoginResponse response = future.get();
```

Đặc điểm:

- Linh hoạt hơn blocking
- Dễ integrate với framework async (CompletableFuture, Guava)
- Phù hợp khi cần control flow rõ ràng hơn async callback

### 4. Lợi ích của gRPC

- Giảm lỗi do không đồng bộ contract
- Tăng tốc độ phát triển
- Dễ maintain khi hệ thống lớn

## II. Cách cách gửi dữ liệu trong gRPC

Trong gRPC, cách client gửi dữ liệu phụ thuộc vào **kiểu RPC được định nghĩa trong `.proto`**. Có các kiểu gửi (RPC types) trong gRPC, định nghĩa **4 kiểu giao tiếp chính** giữa client và server:

### 2.1. Unary RPC

- **Đặc điểm**: 1 request → 1 response
- **Luồng**: Client gửi 1 message, server xử lý và trả về 1 message

**Proto syntax:**

```protobuf
rpc GetUser (UserRequest) returns (UserResponse);
```

### 2.2. Streaming RPC

#### 2.2.1 Server Streaming RPC

- **Đặc điểm**: 1 request → nhiều response (stream từ server)
- **Luồng**: Client gửi 1 message, server trả về nhiều message theo stream

**Proto syntax:**

```protobuf
rpc GetOrders (OrderRequest) returns (stream OrderResponse);
```

#### 2.2.2. Client Streaming RPC

- **Đặc điểm**: nhiều request → 1 response
- **Luồng**: Client gửi nhiều message (stream), server trả về 1 message

**Proto syntax:**

```protobuf
rpc UploadLogs (stream LogRequest) returns (LogResponse);
```

#### 2.2.3. Bidirectional Streaming RPC

- **Đặc điểm**: nhiều request ↔ nhiều response (2 chiều, độc lập)
- **Luồng**: Client và server đều gửi stream song song

**Proto syntax:**

```protobuf
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
```

## III. gRPC Error Handling (Status codes)

Trong gRPC, việc xử lý lỗi không sử dụng HTTP status như REST, mà dựa trên một hệ thống **status code chuẩn hoá** được định nghĩa sẵn. Điều này giúp đảm bảo tính nhất quán giữa các service, bất kể ngôn ngữ hay nền tảng.

### 3.1. Status Code là gì?

**Status code** trong gRPC là mã trạng thái được server trả về để mô tả kết quả của một RPC call.

Mỗi response trong gRPC đều đi kèm:

- **Status code** (bắt buộc)
- **Message mô tả lỗi** (tuỳ chọn)
- **Metadata bổ sung** (tuỳ chọn)

Cấu trúc logic:

```protobuf
status = {
  code: StatusCode,
  description: String,
  metadata: Metadata (optional)
}
```

### 3.2. Các nhóm Status Code phổ biến

gRPC định nghĩa một tập hợp status code chuẩn (theo `io.grpc.Status` trong Java). Các code quan trọng:

#### Nhóm thành công

- **OK (0)**
    
    Request xử lý thành công.
    

#### Nhóm lỗi phía client (4xx tương đương)

- **INVALID_ARGUMENT**
    
    Dữ liệu đầu vào không hợp lệ (validation fail)
    
- **NOT_FOUND**
    
    Không tìm thấy resource
    
- **ALREADY_EXISTS**
    
    Resource đã tồn tại
    
- **FAILED_PRECONDITION**
    
    Trạng thái hệ thống không phù hợp để thực hiện request
    
- **OUT_OF_RANGE**
    
    Giá trị nằm ngoài phạm vi cho phép
    
- **UNAUTHENTICATED**
    
    Chưa xác thực (thiếu/invalid token)
    
- **PERMISSION_DENIED**
    
    Không có quyền truy cập
    

#### Nhóm lỗi phía server (5xx tương đương)

- **INTERNAL**
    
    Lỗi nội bộ server
    
- **UNAVAILABLE**
    
    Service không sẵn sàng (down, timeout, network)
    
- **DEADLINE_EXCEEDED**
    
    Request bị timeout
    
- **RESOURCE_EXHAUSTED**
    
    Hết tài nguyên (rate limit, memory, quota)
    
- **ABORTED**
    
    Operation bị huỷ do conflict
    

#### Nhóm đặc biệt

- **CANCELLED**
    
    Client chủ động huỷ request
    
- **UNKNOWN**
    
    Lỗi không xác định
    
- **UNIMPLEMENTED**
    
    Method chưa được implement
    

### 3.3. Cách gRPC trả lỗi

Trong gRPC, lỗi **không trả qua response message**, mà được đẩy qua **channel error**.

Ví dụ (Server - Java)

```java
public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
    if (request.getUserId().isEmpty()) {
        responseObserver.onError(
            Status.INVALID_ARGUMENT
                .withDescription("userId must not be empty")
                .asRuntimeException()
        );
        return;
    }

    User user = findUser(request.getUserId());

    if (user == null) {
        responseObserver.onError(
            Status.NOT_FOUND
                .withDescription("User not found")
                .asRuntimeException()
        );
        return;
    }

    responseObserver.onNext(toResponse(user));
    responseObserver.onCompleted();
}
```

### 3.4. Phía Client nhận lỗi như thế nào?

Client sẽ nhận lỗi thông qua exception:

```java
try {
    UserResponse res = blockingStub.getUser(request);
} catch (StatusRuntimeException e) {
    Status.Code code = e.getStatus().getCode();

    if (code == Status.Code.NOT_FOUND) {
        // handle not found
    } else if (code == Status.Code.INVALID_ARGUMENT) {
        // handle validation error
    }
}
```

## IV. gRPC Interceptors (Client & Server side)

### 4.1. gRPC Interceptor là gì

**gRPC Interceptor** là cơ chế cho phép chèn logic vào trước hoặc sau khi một RPC được thực thi ở cả phía client và server.

Đặc điểm:

- Hoạt động như một lớp trung gian trong pipeline xử lý request/response
- Không thay đổi business logic chính
- Dùng để xử lý các cross-cutting concerns:
    - Authentication / Authorization
    - Logging
    - Tracing
    - Metrics
    - Retry / Timeout

### 4.2. Client-side Interceptor

Chạy ở phía client trước khi request được gửi đi.

#### Mục đích

- Thêm metadata (authorization, user-id)
- Logging request
- Retry / cấu hình timeout

#### Luồng xử lý

```
Client → ClientInterceptor → gRPC call → Server
```

#### Ví dụ (Java)

```java
public class AuthClientInterceptor implements ClientInterceptor {

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
            MethodDescriptor<ReqT, RespT> method,
            CallOptions callOptions,
            Channel next) {

        return new ForwardingClientCall.SimpleForwardingClientCall<>(
                next.newCall(method, callOptions)) {

            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                headers.put(
                    Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER),
                    "Bearer token-value"
                );
                super.start(responseListener, headers);
            }
        };
    }
}
```

### 4.3. Server-side Interceptor

Chạy ở phía server trước khi request được xử lý bởi service.

#### Mục đích

- Xác thực (authentication)
- Kiểm tra quyền (authorization)
- Logging
- Extract metadata

#### Luồng xử lý

```
Client → ServerInterceptor → Service Handler
```

#### Ví dụ (Java)

```java
public class AuthServerInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
            ServerCall<ReqT, RespT> call,
            Metadata headers,
            ServerCallHandler<ReqT, RespT> next) {

        String token = headers.get(
            Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)
        );

        if (token == null) {
            call.close(Status.UNAUTHENTICATED, headers);
            return new ServerCall.Listener<ReqT>() {};
        }

        return next.startCall(call, headers);
    }
}
```

## V. gRPC Metadata (Header handling)

### 5.1. gRPC Metadata là gì

**gRPC Metadata** là cơ chế truyền **dữ liệu dạng key–value** kèm theo request/response, tương tự như **HTTP headers**.

Đặc điểm:

- Không nằm trong message protobuf (không thuộc payload chính)
- Được gửi kèm trong quá trình gọi RPC
- Dùng để truyền thông tin bổ sung

Các loại dữ liệu thường dùng:

- `authorization` (JWT, token)
- `x-user-id`
- `trace-id`
- `request-id`

### 5.2. Vai trò của Metadata

Metadata dùng để xử lý các thông tin ngoài business data:

- **Authentication / Authorization**
    - Truyền token từ client → server
- **Tracing / Logging**
    - Truyền `trace-id` xuyên suốt hệ thống
- **Context propagation**
    - Truyền thông tin user, locale, timezone
- **Custom headers**
    - Các thông tin nội bộ giữa các service

### 5.3. Phân loại Metadata

Trong gRPC, metadata được chia thành:

#### (1) Request Metadata (Headers)

- Gửi từ client → server
- Được xử lý trước khi service chạy

#### (2) Response Metadata (Headers & Trailers)

- Server → client
- Bao gồm:
    - Headers (gửi sớm)
    - Trailers (gửi khi kết thúc response)

### 5.4. Cách hoạt động trong luồng gRPC

```
Client → Metadata (headers) → Server
Server → Metadata (headers/trailers) → Client
```

- Client attach metadata vào request
- Server đọc metadata trong interceptor hoặc handler
- Server có thể trả lại metadata cho client

### 5.5. Cách sử dụng Metadata (Java)

#### 5.5.1. Tạo Metadata Key

```java
Metadata.Key<String> AUTHORIZATION =
    Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
```

- Key phải khai báo với kiểu dữ liệu
- Thường dùng:
    - `ASCII_STRING_MARSHALLER`
    - `BINARY_BYTE_MARSHALLER` (cho dữ liệu nhị phân)

#### 5.5.2. Gửi Metadata từ Client

```java
Metadata metadata = new Metadata();
metadata.put(AUTHORIZATION, "Bearer token-value");

stub = MetadataUtils.attachHeaders(stub, metadata);
```

#### 5.5.3. Nhận Metadata ở Server (Interceptor)

```java
String token = headers.get(AUTHORIZATION);
```

- Thường đọc trong **ServerInterceptor**
- Sau đó:
    - Validate token
    - Gắn vào context

#### 5.5.4. Gửi Metadata từ Server về Client

```java
Metadata responseHeaders = new Metadata();
responseHeaders.put(
    Metadata.Key.of("custom-header", Metadata.ASCII_STRING_MARSHALLER),
    "value"
);

call.sendHeaders(responseHeaders);
```

### 5.6. Quy tắc và lưu ý

- Key phải viết thường (lowercase)
- Key kết thúc bằng `bin` nếu là binary
- Metadata không dùng để thay thế payload chính, vì
- Kích thước metadata có giới hạn (tùy implementation, thường vài KB)

### 5.7. Kết hợp với Interceptor

Metadata thường được xử lý thông qua interceptor:

#### Client Interceptor có nhiệm vụ gắn metadata vào request

```
Client → Interceptor → attach metadata → gửi request
```

#### Server Interceptor được dùng để đọc và xử lý metadata

```
Nhận request → Interceptor → đọc metadata → validate → handler
```

## VI. Syntax cơ bản của gRPC (Protobuf IDL)

Một file `.proto` thường gồm các thành phần chính:

- Syntax version

```protobuf
syntax = "proto3";
```

- Package (namespace)

```protobuf
package user;
```

- Option (tuỳ chọn cho code generation)

Ví dụ Java:

```protobuf
option java_package = "com.example.user";
option java_multiple_files = true;
```

- Message (định nghĩa data structure)

```protobuf
message UserRequest {
  string user_id = 1;
}

message UserResponse {
  string name = 1;
  int32 age = 2;
}
```

- Mỗi field có:
    - kiểu dữ liệu (`string`, `int32`, …)
    - **field number** (1, 2, 3, …) → dùng cho serialization
- Service (định nghĩa API)

```protobuf
service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
}
```

- Các kiểu dữ liệu thường dùng
    - Scalar types:
        - `string`, `int32`, `int64`, `bool`, `bytes`, …
    - Repeated (array):
        
        ```protobuf
        repeated string tags = 3;
        ```
        
    - Map:
        
        ```protobuf
        map<string, string> metadata = 4;
        ```
        
- Enum

```protobuf
enum Status {
  UNKNOWN = 0;
  ACTIVE = 1;
  INACTIVE = 2;
}
```

- Nested message (lồng nhau)

```protobuf
message Order {
  string id = 1;

  message Item {
    string name = 1;
    int32 quantity = 2;
  }

  repeated Item items = 2;
}
```

- Tổng kết cấu trúc một file `.proto`

```protobuf
syntax = "proto3";

package example;

option java_package = "com.example";

message Request {
  string id = 1;
}

message Response {
  string result = 1;
}

service ExampleService {
  rpc ExampleMethod (Request) returns (Response);
}
```

