앞전 클라이언트 코드에서 Stub을 만들어준 적이 있다.
@Slf4j
@RequiredArgsConstructor
@Component
public class GrpcMemberClient {
@GrpcClient("member-service")
private Channel channel; // gRPC 채널 재사용
// gRPC 클라이언트는 Feign처럼 자동 구성되는 것이 아니라 직접 호출 시점에 서킷 브레이커를 적용하는 방식으로 구성해야 합니다.
private final CircuitBreakerRegistry circuitBreakerRegistry;
private CircuitBreaker grpcCircuitBreaker;
// Stub을 재사용하기 위해 필드로 선언
private MemberServiceGrpc.MemberServiceBlockingStub stub;
@PostConstruct
public void init() {
// "grpcCircuitBreaker" 이름으로 서킷 브레이커 생성 (설정은 기본값 또는 별도 구성한 값 사용)
grpcCircuitBreaker = circuitBreakerRegistry.circuitBreaker("grpcCircuitBreaker");
// 채널로부터 Stub을 한 번만 생성
**stub = MemberServiceGrpc.newBlockingStub(channel);**
}
간단하게 한 줄로 표현하자면
메서드 호출처럼 보이게 함
→ 실제로는 네트워크 요청인데, 그냥 stub.registerUser(request) 같은 코드로 사용 가능.
요청을 직렬화(Serialize)해서 서버로 전송
→ Java 객체 → protobuf 바이너리 형태로 변환해서 gRPC 서버로 보냄.
응답 수신 및 역직렬화(Deserialize)
→ 서버에서 온 응답을 protobuf → Java 객체로 바꿔서 돌려줌.
코드로 이해해보자
@GrpcClient("member-service")
private Channel channel; // gRPC 채널 재사용
// Stub을 재사용하기 위해 필드로 선언
private MemberServiceGrpc.MemberServiceBlockingStub stub;
@PostConstruct
public void init() {
// "grpcCircuitBreaker" 이름으로 서킷 브레이커 생성 (설정은 기본값 또는 별도 구성한 값 사용)
grpcCircuitBreaker = circuitBreakerRegistry.circuitBreaker("grpcCircuitBreaker");
// 채널로부터 Stub을 한 번만 생성
stub = MemberServiceGrpc.newBlockingStub(channel);
}
private MemberProto.MemberResponse performGrpcCall(Long memberId) {
// gRPC 요청 객체 생성
MemberProto.MemberIdRequest request = MemberProto.MemberIdRequest.newBuilder()
.setId(memberId)
.build();
// 현재 인증 정보 로깅 (SecurityContext에서 가져옴)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
log.trace("Current Authentication: {}", authentication);
try {
return stub.getMemberById(request); //실제 네트워크 호출!!
} catch (StatusRuntimeException e) {
log.error("gRPC 호출 실패 - 상태: {}, 설명: {}, 원인: {}",
e.getStatus(),
e.getStatus().getDescription(),
e.getCause());
throw e;
}
}
프론트에서 요청을 보냄 → gRPC-Client-application의 컨트롤러 단으로 요청이 들어감