Search
🙈

grpc 서버 구현 예제

Intro::

grpc 서버 구현 예제입니다.

gRPC란 무엇인가?

gRPC는 구글이 만든 고성능 원격 호출(Remote Procedure Call) 프레임워크입니다.
쉽게 말해, 멀리 떨어진 컴퓨터의 함수를 우리 프로그램 안의 함수처럼 부를 수 있게 해 주는 통신 규칙입니다. HTTP/2 위에서 동작하며, 데이터를 **Protocol Buffers(프로토콜 버퍼)**라는 작고 빠른 이진 형식으로 주고받습니다.

동작 과정 한눈에 보기

1.
인터페이스 약속
.proto 파일에 “어떤 함수 이름으로, 어떤 자료를 주고받겠다” 라고 약속을 적습니다.
2.
코드 자동 생성
protoc 컴파일러가 약속을 읽어 서버·클라이언트용 “대리 함수(Stub)” 코드를 만들어 줍니다.
3.
서버 구현
서버는 생성된 추상 클래스를 상속해 실제 로직만 채웁니다.
4.
클라이언트 호출
클라이언트는 Stub 함수를 호출합니다.
Stub이 HTTP/2로 메시지를 이진 직렬화해 서버로 전송합니다.
5.
응답 수신
서버가 결과를 만들어 Stub에 돌려주면, Stub이 다시 객체로 역직렬화해 호출자에게 반환합니다.

장점

구분
설명
속도·효율
HTTP/2 멀티플렉싱 + 이진 직렬화 덕분에 REST/JSON 대비 지연시간과 대역폭이 작습니다.
엄격한 스키마
.proto 파일 하나로 문서화·검증·코드 생성을 동시에 해결합니다.
양방향 스트리밍
한 연결에서 서버클라이언트가 실시간으로 여러 메시지를 주고받을 수 있습니다.
다양한 언어 지원
C++, Java, Go, Python 등 10여 개 이상 공식 라이브러리를 제공합니다.
보안 기본 제공
TLS 암호화를 권장값으로 내장해 두었습니다.

단점

구분
설명
브라우저 직접 호출 어려움
HTTP/2의 이진 프레임을 그대로 다룰 수 없어서, 웹에서는 gRPC‑Web 프록시가 필요합니다.
디버깅·모니터링 불편
JSON처럼 눈으로 바로 읽을 수 없어, 별도 툴이나 로그 뷰어가 필요합니다.
스키마 강제 관리
.proto 변경 시 서버·클라이언트를 함께 업데이트해야 하므로, 버전 호환 전략이 필수입니다.
대용량 파일 전송 부적합
주로 작은 메시지에 최적화되어 있어, 큰 파일은 별도 스토리지나 REST API가 더 편할 수 있습니다.
인프라 요구
로드밸런서·프록시가 HTTP/2를 지원해야 하며, 일부 구형 환경에서는 설정이 복잡합니다.

언제 쓰면 좋은가?

마이크로서비스 내부 통신 – 빠르고 타입 안정성이 높은 통신이 필요할 때
모바일·IoT – 네트워크가 느리거나 데이터 요금이 민감할 때
실시간 스트리밍 – 채팅·센서 데이터처럼 양방향 흐름이 끊기지 않아야 할 때
반대로 공개 REST API처럼 브라우저·서드파티 접근성이 중요한 서비스라면, REST + JSON을 유지하고 내부 전용 채널에만 gRPC를 도입하는 하이브리드 구조가 실무에서 흔히 쓰입니다.

정리

gRPC는 빠르고, 작고, 명확한 약속을 강점으로 삼지만
브라우저 호환·디버깅 난이도라는 약점을 가집니다.
서비스 특성과 팀의 운영 여건을 고려해 REST와 gRPC를 상황별로 섞어 쓰는 전략이 가장 현실적인 선택입니다.

프로젝트 구조

grpc_example/ ├── build.gradle.kts ├── settings.gradle.kts ├── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/example/ │ │ │ ├── GrpcExampleApplication.kt │ │ │ └── GreetingServiceImpl.kt │ │ ├── proto/ │ │ │ └── greeting.proto │ │ └── resources/ │ │ └── application.yml
Plain Text
복사

build.gradle.kts

/* --------------------------------------------------------------------------- * build.gradle.kts * └ Kotlin DSL로 작성한 Spring Boot + gRPC 프로젝트 예시 * ------------------------------------------------------------------------- */ import com.google.protobuf.gradle.* // ← protobuf DSL(프로토·gRPC 설정)을 인식시키기 위해 반드시 import plugins { kotlin("jvm") version "1.9.25" kotlin("plugin.spring") version "1.9.25" id("org.springframework.boot") version "3.4.4" id("io.spring.dependency-management") version "1.1.7" id("com.google.protobuf") version "0.9.4" // ← proto 컴파일 태스크를 자동 생성하는 Gradle 플러그인 } group = "com.example" version = "0.0.1-SNAPSHOT" java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } repositories { mavenCentral() } dependencies { /* ──────────────── Spring Boot 기본 의존성 ──────────────── */ implementation("org.springframework.boot:spring-boot-starter") implementation("org.jetbrains.kotlin:kotlin-reflect") /* ──────────────── 테스트 의존성 ──────────────── */ testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testRuntimeOnly("org.junit.platform:junit-platform-launcher") /* ──────────────── gRPC 서버 구동 관련 의존성 ──────────────── */ implementation("net.devh:grpc-server-spring-boot-starter:2.15.0.RELEASE") // ↳ Spring Boot와 gRPC Netty 서버를 손쉽게 통합(@GrpcService 자동 빈 등록·포트 바인딩) implementation("javax.annotation:javax.annotation-api:1.3.2") // ↳ gRPC Stub 코드에서 @Generated 등 자바 표준 어노테이션을 사용하므로 필요 /* ──────────────── gRPC 메시지 직렬화·Stub 라이브러리 ──────────────── */ implementation("com.google.protobuf:protobuf-java:4.28.2") // ↳ proto 메시지를 자바 객체로 (역)직렬화하는 핵심 라이브러리 implementation("io.grpc:grpc-protobuf:1.58.0") // ↳ gRPC Java가 protobuf 메시지를 네트워크 스트림에 인코딩/디코딩할 때 사용 implementation("io.grpc:grpc-stub:1.58.0") // ↳ *Grpc.java / *GrpcKt.kt Stub 클래스에서 공통으로 참조되는 API } /* --------------------------------------------------------------------------- * Protocol Buffers & gRPC 코드 생성 설정 * ------------------------------------------------------------------------- */ protobuf { /* 1) protoc 컴파일러 지정: .proto → Java/Kotlin 소스 변환 */ protoc { artifact = "com.google.protobuf:protoc:3.24.0" // 버전을 고정해 환경 간 재현성 확보 } /* 2) gRPC 코드 생성 플러그인 등록 */ plugins { id("grpc") { // 플러그인 이름은 임의; 여기선 "grpc" artifact = "io.grpc:protoc-gen-grpc-java:1.58.0" // ↳ protoc 실행 시 --grpc_out 옵션에 해당. service 정의 → *Grpc.java 생성 } } /* 3) 프로젝트 내 모든 proto 컴파일 태스크에 gRPC 플러그인 적용 */ generateProtoTasks { all().forEach { task -> task.plugins { id("grpc") // 위에서 정의한 플러그인 활성화 } // 필요 시 task.generateDescriptorSet = true 등 세부 옵션도 추가 가능 } } } /* --------------------------------------------------------------------------- * Kotlin 컴파일러 옵션 및 테스트 설정 * ------------------------------------------------------------------------- */ kotlin { compilerOptions { freeCompilerArgs.addAll("-Xjsr305=strict") // Nullable · JSR‑305 어노테이션을 엄격 모드로 } } tasks.withType<Test> { useJUnitPlatform() // JUnit 5 플랫폼 사용 }
Plain Text
복사

greeting.proto

// ─────────────────────────────────────────────────────────────── // greeting.proto // · gRPC 서비스 인터페이스와 메시지 정의 파일 // · proto3 문법을 사용하여 인사(greeting) 관련 API를 기술 // ─────────────────────────────────────────────────────────────── // 1) proto 문법 버전 지정 ------------------------------------------------- // • proto2 / proto3 중 하나를 선택해야 하며 // • 최신 gRPC‑Java, Kotlin 환경에서는 proto3 사용이 일반적 syntax = "proto3"; // 2) 패키지 네임스페이스 -------------------------------------------------- // • gRPC 내부에서 서비스·메시지를 구분할 때 사용 // • Java/Kotlin 패키지와 무관(로직상 구분자 역할) package greeting; // 3) Java 코드 생성 옵션 -------------------------------------------------- // 3‑1) java_package // · protoc가 생성할 *.java 파일의 실제 패키지 경로 지정 // · 관례적으로 도메인 역순(com.example.grpc) 사용 option java_package = "com.example.grpc"; // 3‑2) java_multiple_files // · true : 메시지·서비스마다 개별 .java 파일 생성 // · false : 하나의 외부 클래스 안에 중첩 클래스로 생성 // · true 로 두면 Git 충돌·컴파일 시간 측면에서 유리 option java_multiple_files = true; // 4) 서비스 정의 ---------------------------------------------------------- // • GreetingService는 “인사하기” 기능을 외부에 노출 // • rpc 메서드마다 요청·응답 메시지 타입을 지정 service GreetingService { // 4‑1) sayHello // · 단일 요청(HelloRequest)을 받아 단일 응답(HelloResponse)을 돌려줌 // · 스트리밍이 필요하면 `stream` 키워드를 사용 rpc sayHello(HelloRequest) returns (HelloResponse); } // 5) 메시지 타입 정의 ------------------------------------------------------ // • 각 필드는 “타입 + 이름 = 태그번호” 형식 // • 태그번호는 바이너리 직렬화 시 키 역할(1~2³²‑1) // • 중간에 삭제·변경 시 호환성 문제를 피하려면 번호 재사용 금지 message HelloRequest { string name = 1; // 클라이언트가 전송하는 이름(문자열) } message HelloResponse { string message = 1; // 서버가 반환하는 인사말 }
Protobuf
복사

GreetingServiceImpl

package com.example.grpc import io.grpc.stub.StreamObserver import net.devh.boot.grpc.server.service.GrpcService /** * gRPC 서버 측 구현체. * * ① @GrpcService * ────────────────────────────────────────────────────────────── * • grpc‑spring‑boot‑starter 가 스프링 빈으로 등록하고 * Netty 서버에 자동 바인딩하도록 하는 어노테이션 * * ② GreetingServiceGrpc.GreetingServiceImplBase * ────────────────────────────────────────────────────────────── * • protoc + protoc‑gen‑grpc‑java 가 생성한 추상 클래스 * • proto 파일에 정의한 GreetingService 의 ‘서버 골격’(skeleton) * • 필요한 rpc 메서드(sayHello 등)를 오버라이드해 로직 작성 */ @GrpcService class GreetingServiceImpl : GreetingServiceGrpc.GreetingServiceImplBase() { /** * 단일 요청‑응답 RPC 메서드 구현. * * @param request 클라이언트가 보낸 HelloRequest(name 필드 포함) * @param responseObserver 서버 → 클라이언트로 응답을 push 하는 스트림 */ override fun sayHello( request: HelloRequest, responseObserver: StreamObserver<HelloResponse> ) { // 1) 비즈니스 로직: 인사말 생성 val greetingMessage = "안녕하세요, ${request.name}님!" // 2) 응답 메시지(HelloResponse) 빌드 val response = HelloResponse.newBuilder() .setMessage(greetingMessage) .build() // 3) 응답 전송 responseObserver.onNext(response) // 실제 데이터 push responseObserver.onCompleted() // 스트림 종료 알림 } }
Kotlin
복사

GrpcExampleApplication

package com.example.grpc import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class GrpcExampleApplication fun main(args: Array<String>) { runApplication<GrpcExampleApplication>(*args) }
Kotlin
복사

application.yml

grpc: server: port: 9090
YAML
복사

References::