armeira gRpc Stub 종류와 차이점
이 포스트에서는 armeira 환경에서 사용되는 gRPC Stub의 종류와 그 차이점에 대해 초보자도 이해할 수 있도록 쉽게 설명합니다. 각 Stub의 생성 방식, 사용 목적, 특징을 실제 예제와 함께 비교합니다.
gRPC란 무엇인가?
먼저 gRPC에 대해 간단히 짚고 넘어가겠습니다. gRPC는 Google에서 개발한 오픈소스 원격 프로시저 호출(Remote Procedure Call, RPC) 프레임워크로, 다양한 언어를 지원하며 HTTP/2 기반의 빠르고 효율적인 통신을 제공합니다. Kotlin, Java, Python 등 다양한 언어에서 사용할 수 있어 마이크로서비스 아키텍처에 적합합니다.
armeira에서 gRPC Stub의 역할
armeira와 같은 분산 시스템에서 gRPC Stub은 클라이언트와 서버 간의 통신을 추상화하는 역할을 합니다. Stub은 클라이언트가 마치 로컬 함수처럼 원격 서버의 메서드를 호출할 수 있도록 해줍니다. gRPC에서는 주로 3가지 Stub을 제공합니다.
gRPC Stub의 종류
- Blocking Stub (또는 Synchronous Stub)
- 동기 방식으로 동작합니다.
- 메서드 호출이 완료될 때까지 호출한 스레드가 블록됩니다.
- 사용 예시 (Kotlin):
val blockingStub = MyServiceGrpc.newBlockingStub(channel) val response = blockingStub.myMethod(request)
- 주로 간단한 요청-응답 패턴, 테스트 코드, 동기 처리가 필요한 곳에 사용됩니다.
- Future Stub
- 비동기 방식이지만, Java의
ListenableFuture
를 반환합니다. - 결과를 나중에 받을 수 있고, 콜백이나 Future의 get()을 통해 결과를 처리할 수 있습니다.
- 사용 예시 (Kotlin):
val futureStub = MyServiceGrpc.newFutureStub(channel) val future = futureStub.myMethod(request) val response = future.get() // 블로킹 호출
- 주로 Java 환경에서 많이 사용되며, Kotlin에서는 코루틴과의 궁합이 약간 떨어질 수 있습니다.
- 비동기 방식이지만, Java의
- Async Stub (비동기 Stub)
- 완전 비동기 방식으로 동작합니다.
- 메서드 호출 시 콜백(Observer)을 통해 결과를 비동기적으로 받습니다.
- 사용 예시 (Kotlin):
val asyncStub = MyServiceGrpc.newStub(channel) asyncStub.myMethod(request, object : StreamObserver<MyResponse> { override fun onNext(value: MyResponse) { // 응답 처리 } override fun onError(t: Throwable) { // 에러 처리 } override fun onCompleted() { // 완료 처리 } })
- 실시간 데이터 스트리밍, 대용량 데이터 처리, 비동기 처리가 필요한 곳에 적합합니다.
Stub 종류별 주요 차이점
종류 | 동작 방식 | 반환값 | 사용 예시 | 적합한 상황 |
---|---|---|---|---|
Blocking Stub | 동기 | 직접 반환 | blockingStub.method | 단순 요청-응답, 테스트 |
Future Stub | 비동기 | ListenableFuture | futureStub.method | Java 비동기, 콜백 필요시 |
Async Stub | 비동기 | 콜백(Observer) | asyncStub.method | 실시간/스트리밍, 대규모 처리 |
armeira에서의 선택 기준
- Blocking Stub: 간단한 테스트나 샘플 코드, 빠른 결과가 필요한 곳에서 사용합니다.
- Future Stub: Java 기반 프로젝트나, Future 패턴이 필요한 곳에서 사용합니다.
- Async Stub: 실시간 데이터 처리, 대규모 비동기 작업, 스트리밍 등에서 사용합니다.
- Kotlin에서는 코루틴과 함께 사용할 경우, Async Stub을 래핑하여 사용하는 것이 일반적입니다.
샘플 코드 (Kotlin)
Blocking Stub 예시:
val blockingStub = MyServiceGrpc.newBlockingStub(channel)
val response = blockingStub.myMethod(request)
Async Stub + 코루틴 예시:
suspend fun callAsyncStub(channel: ManagedChannel, request: MyRequest): MyResponse = suspendCoroutine { cont ->
val asyncStub = MyServiceGrpc.newStub(channel)
asyncStub.myMethod(request, object : StreamObserver<MyResponse> {
override fun onNext(value: MyResponse) {
cont.resume(value)
}
override fun onError(t: Throwable) {
cont.resumeWithException(t)
}
override fun onCompleted() {}
})
}
Spring Boot + Kotlin + Armeria + gRPC 환경에서 Async Stub 활용 예시
실제 서비스 환경에서는 gRPC의 Async Stub을 통해 외부 gRPC 서버에 비동기적으로 요청을 보내고, 받은 응답을 바탕으로 비즈니스 로직(서비스 클래스의 메서드)을 처리한 뒤 최종 결과를 반환해야 하는 경우가 많습니다. 아래는 Spring Boot 3.4.5, Kotlin, Armeria 1.32.5, gRPC 조합에서 이를 구현하는 대표적인 패턴 예시입니다.
예시 구조
GrpcClientService
: gRPC Async Stub을 통해 비동기 요청을 보내고 코루틴으로 래핑해 결과를 받는 서비스BusinessService
: 실제 비즈니스 로직을 담당하는 서비스 클래스Controller
또는 gRPC 서버 구현체: 위 두 서비스를 조합하여 최종 응답 반환
1. GrpcClientService (Async Stub + 코루틴)
@Service
class GrpcClientService(
private val channel: ManagedChannel
) {
private val asyncStub = MyServiceGrpc.newStub(channel)
suspend fun callRemoteService(request: MyRequest): MyResponse = suspendCoroutine { cont ->
asyncStub.myMethod(request, object : StreamObserver<MyResponse> {
override fun onNext(value: MyResponse) {
cont.resume(value)
}
override fun onError(t: Throwable) {
cont.resumeWithException(t)
}
override fun onCompleted() {}
})
}
}
2. BusinessService (비즈니스 로직 처리)
@Service
class BusinessService {
fun process(response: MyResponse): String {
// 실제 비즈니스 처리 (예: 데이터 가공, DB 저장 등)
return "처리 결과: ${response.result}"
}
}
3. Controller/gRPC 서버 구현체 (전체 흐름 조합)
@RestController
@RequestMapping("/api")
class MyController(
private val grpcClientService: GrpcClientService,
private val businessService: BusinessService
) {
@GetMapping("/call-remote")
suspend fun callRemote(): ResponseEntity<String> {
val request = MyRequest.newBuilder().setParam("test").build()
val response = grpcClientService.callRemoteService(request)
val result = businessService.process(response)
return ResponseEntity.ok(result)
}
}
위 예시는 Spring Boot + Kotlin + Armeria 환경에서 gRPC Async Stub을 활용해 비동기적으로 외부 gRPC 서버와 통신하고, 받은 응답을 별도의 서비스 클래스에서 처리하여 최종적으로 REST API로 결과를 반환하는 전형적인 패턴입니다. 실제 환경에서는 예외 처리, 로깅, 트랜잭션 관리 등이 추가될 수 있습니다.
참고자료
각 Stub의 특성을 이해하고 상황에 맞게 선택하면, armeira와 같은 분산 시스템에서 gRPC를 더욱 효과적으로 활용할 수 있습니다.