Tích hợp GRPC

Holy_DevHoly_Dev
4 min read

Mục tiêu

Chuyển từ:

order-service gọi user-service qua Feign + HTTP REST

Sang:

order-service gọi user-service qua gRPC để lấy UserDto theo userId.


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


1. Tạo file user.proto

Tạo tại user-service/src/main/proto/user.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.userservice.grpc";
option java_outer_classname = "UserProto";

service UserGrpcService {
  rpc GetUserById (GetUserRequest) returns (UserResponse);
}

message GetUserRequest {
  int64 userId = 1;
}

message UserResponse {
  int64 id = 1;
  string name = 2;
  string email = 3;
}

2. Cấu hình Gradle cho user-service

user-service/build.gradle

plugins {
    id 'java'
    id 'com.google.protobuf' version '0.9.4'
}

dependencies {  
    //grpc
    implementation 'net.devh:grpc-server-spring-boot-starter:2.15.0.RELEASE'
    implementation 'net.devh:grpc-client-spring-boot-starter:2.15.0.RELEASE'

    // Ghim phiên bản GRPC core để tương thích
    implementation 'io.grpc:grpc-core:1.54.0'
    implementation 'io.grpc:grpc-netty-shaded:1.54.0'
    implementation 'io.grpc:grpc-protobuf:1.54.0'
    implementation 'io.grpc:grpc-stub:1.54.0'

    // Cho phép dùng javax.annotation
    implementation 'javax.annotation:javax.annotation-api:1.3.2'
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.25.3"
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:1.64.0"
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.plugins {
                grpc {}
            }
        }
    }
}

3. Cài đặt gRPC server ở user-service

Tạo class UserGrpcServiceImpl

import com.example.userservice.model.User;
import com.example.userservice.repository.UserRepository;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
@RequiredArgsConstructor
public class UserGrpcServiceImpl extends UserGrpcServiceGrpc.UserGrpcServiceImplBase {

    private final UserRepository userRepository;

    @Override
    public void getUserById(GetUserRequest request, StreamObserver<UserResponse> responseObserver) {
        User user = userRepository.findById(request.getUserId())
                .orElseThrow(() -> new RuntimeException("User not found"));

        UserResponse response = UserResponse.newBuilder()
                .setId(user.getId())
                .setName(user.getName())
                .setEmail(user.getEmail())
                .build();

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

4. Cấu hình gRPC client tại order-service

order-service/build.gradle

plugins {
    id 'java'
    id 'com.google.protobuf' version '0.9.4'
}

dependencies {  
     //grpc
    implementation 'net.devh:grpc-client-spring-boot-starter:2.15.0.RELEASE'

    // Ghim phiên bản GRPC core để tương thích
    implementation 'io.grpc:grpc-core:1.54.0'
    implementation 'io.grpc:grpc-netty-shaded:1.54.0'
    implementation 'io.grpc:grpc-protobuf:1.54.0'
    implementation 'io.grpc:grpc-stub:1.54.0'

    // Cho phép dùng javax.annotation
    implementation 'javax.annotation:javax.annotation-api:1.3.2'
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.25.3"
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:1.64.0"
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {}
            }
            task.plugins {
                grpc {}
            }
        }
    }
}

👉 Copy file user.proto giống như bên user-service vào: order-service/src/main/proto/user.proto ( nhớ thay đổi package name nha)


5. Cấu hình application.yml

user-service

grpc:
  server:
    port: 9090

order-service

grpc:
  client:
    user-service:
      address: static://localhost:9090
      negotiationType: plaintext

6. Tạo gRPC client trong order-service

import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;

@Service
public class GrpcUserClient {

    @GrpcClient("user-service")
    private UserGrpcServiceGrpc.UserGrpcServiceBlockingStub userStub;

    public UserResponse getUserById(Long id) {
        GetUserRequest request = GetUserRequest.newBuilder().setUserId(id).build();
        return userStub.getUserById(request);
    }
}

7. Gọi gRPC thay cho Feign trong OrderController

//call gprc client
private final GrpcUserClient grpcUserClient;
@GetMapping("/grpc/{id}")
public OrderResponse getOrderGrpc(@PathVariable Long id) {
    Order order = orderRepository.findById(id).orElseThrow();

    // Gọi gRPC client để lấy thông tin user từ user-service
    UserResponse user = grpcUserClient.getUserById(order.getUserId());

    UserDto userDto = new UserDto(user.getId(), user.getName(), user.getEmail());
    return new OrderResponse(order.getId(), order.getProduct(), order.getPrice(), userDto);
}

Test:

Gọi gRPC API để lấy order + user:

http://localhost:8082/api/orders/grpc/1

Kết quả:

{
    "orderId": 1,
    "product": "Macbook Pro",
    "price": 2500.0,
    "user": {
        "id": 1,
        "name": "user1",
        "email": "holyne@gmail.com"
    }
}

Tổng kết

Đây là luồng khi client gọi đến gRPC server để lấy thông tin user:

Client gọi getUserById(id)
    ↓
gRPC server nhận request (GetUserRequest)
    ↓
Tìm trong DB (userRepository.findById)
    ↓
Tạo response (UserResponse)
    ↓
Gửi lại cho client (onNext + onCompleted)

Tóm tắt flow gRPC giữa 2 services

  1. order-service gọi GrpcUserClient.getUserById(id)

  2. Tạo GetUserRequest

  3. Gửi RPC request đến user-service

  4. user-service xử lý trong UserGrpcServiceImpl

  5. Trả về UserResponse

  6. order-service nhận được kết quả


Ưu điểm của gRPC trong trường hợp này:

  • Gọi hàm từ xa như gọi local method (userStub.getUserById)

  • Truyền data hiệu quả (binary, nhẹ, nhanh)

  • Có type-safe rõ ràng (UserResponse, GetUserRequest...)

So sánh Feign vs gRPC trong hệ thống của bạn

Tiêu chíFeign (REST)gRPC
Tốc độTrung bình (JSON, HTTP/1.1)Nhanh hơn (Protocol Buffers, HTTP/2)
Tự động sinh mãKhông (dùng interface thủ công)Có (dùng .proto)
Kết nối servicesDễ dùng (vì là REST)Phức tạp hơn nhưng tối ưu
Gỡ lỗiDễ (xem được JSON rõ ràng)Khó hơn (phải dùng tools riêng)
Phù hợpGiao tiếp với client/webGiao tiếp service nội bộ tốc độ cao

Gợi ý nâng cao

  • Tách proto/ thành module dùng chung như common-proto

  • Dùng protobuf plugin caching để không build lại toàn bộ mỗi lần

0
Subscribe to my newsletter

Read articles from Holy_Dev directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Holy_Dev
Holy_Dev

strong desire for learning new things