gRPC Reference - 예제

전 편에서 gRPC에 대한 설명과 특징을 알아보았습니다. 이번 포스트에서는 grpc-java 모듈을 활용한 몇 가지 예제코드를 소개하고, 실제 프로젝트에 적용하는 방안에 대해 소개해보겠습니다.

gRPC Basic

  1. 프로젝트 생성
  2. Java : 1.8
  3. gRPC : 1.25.0
  4. ProtoBuf : 3.11.0
  5. pom.xml 설정
  6. gRPC + Protobuf 의존성 설정
  7. 스텁 생성을 위해 protoc 플러그인 설정

    • proto 컴파일 -> 애플리케이션 컴파일 순으로 진행되도록 설정
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nhn.gia</groupId>
    <artifactId>gia-grpc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <grpc.version>1.25.0</grpc.version>
        <protobuf.version>3.11.0</protobuf.version>
        <protoc.version>3.11.0</protoc.version>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <scope>runtime</scope>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>
    </dependencies>

    <build>
        <!-- Maven OS Extension -->
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <!-- proto (IDL) generator plugin -->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protoc.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>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  1. proto 파일 작성 (서비스 정의)
  2. proto 파일을 별도로 컴파일할 경우 mvn protobuf:{goal} 명령어로 컴파일 할 수 있다.
  3. 컴파일된 결과물은 target(혹은 지정 빌드 디렉토리)/generated-sources/protobuf/java 이하 디렉토리에 생성된다.
  4. message 는 서버-클라이언트 간 송수신될 데이터 포맷
  5. service 는 서버, 클라이언트에서 원격 호출되는 서비스 메소드
syntax = "proto3";

package proto_test;

option java_package = "com.nhn.gia.grpc.proto";
option java_multiple_files = true;

service EchoService {
    rpc Echo (EchoRequest) returns (EchoResponse) {}
}

message EchoRequest {
    string msg = 1;
}

message EchoResponse {
    string msg = 1;
}
  1. 서버 프로그램 작성
  2. 서비스 기능 구현: proto파일에서 작성한 service 의 RPC 메소드들을 구현해줌
  3. 아래 예시에서는 정의된 rpc Echo (EchoRequest) returns (EchoResponse) {} 를 구현
public class EchoServiceImpl extends EchoServiceGrpc.EchoServiceImplBase {
    @Override
    public void echo(EchoRequest request, StreamObserver<EchoResponse> responseObserver) {
        EchoResponse response = EchoResponse.newBuilder().setMsg(request.getMsg()).build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}
  • 작성한 서비스를 사용하는 서버 프로그램 작성

    public class EchoServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        Server server = ServerBuilder.forPort(8000)
                .addService(new EchoServiceImpl())
                .build();
    
        server.start();
    
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            server.shutdown();
        }));
    
        server.awaitTermination();
    }
    }
  • 클라이언트 프로그램 작성
public class EchoClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("localhost", 8000)
                .usePlaintext()
                .build();

        EchoServiceBlockingStub stub = EchoServiceGrpc.newBlockingStub(channel);
        EchoResponse response = stub.echo(EchoRequest.newBuilder().setMsg("Hello gRPC!!").build());
        Logger.log("Echo Client: %s", response.getMsg());
        channel.shutdown();
    }

}

gRPC with SpringBoot

스프링 환경에서도 gRPC를 사용할 수 있다. 공식은 아니지만 몇 가지 개인 또는 그룹에서 제작해서 사용하고 있는 Spring boot starter가 있다. (https://github.com/LogNet/grpc-spring-boot-starter) (https://github.com/saturnism/spring-boot-starter-grpc)

아래는 별도 Spring Boot Starter 없이 진행한 예제

  1. 프로젝트 생성, pom.xml 에 gRPC 관련 의존성 추가, proto 파일 작성은 기존과 동일
  2. proto
syntax = "proto3";

option java_package = "com.nhn.gia.proto";
option java_multiple_files = true;

service UserService {
	rpc GetUser(UserSearchRequest) returns (User) {}
}

message UserSearchRequest {
	string id = 1;
}

message User {
	string _id = 1;
	int64 index = 2;
	bool isActive = 3;
	int32 age = 4;
	string name = 5;
	string gender = 6;
	string email = 7;
}
  1. 컴포넌트 정의
  2. 서비스 작성
@Service
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    private final UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public void getUser(UserSearchRequest request, StreamObserver<User> responseObserver) {
        String requestId = request.getId();
        logger.info("requestId: {}", requestId);
        UserEntity entity = userRepository.findOne(requestId);

        responseObserver.onNext(buildUser(entity));
        responseObserver.onCompleted();
    }

    private User buildUser(UserEntity entity) {
        return User.newBuilder()
                .setId(entity.getId())
                .setIndex(entity.getIndex())
                .setIsActive(entity.getIsActive())
                .setAge(entity.getAge())
                .setName(entity.getName())
                .setGender(entity.getGender())
                .setEmail(entity.getEmail())
                .build();
    }
}
  • gRPC 서버 설정
@Configuration
public class GrpcServerConfiguration {

    @Value("${grpc.server.port}")
    private int grpcServerPort;

    @Bean
    public Server grpcServer(UserServiceImpl userService) {
        return ServerBuilder.forPort(grpcServerPort)
                .addService(userService)
                .build();
    }

}
  • Application 구동 시 gRPC 서버가 실행되도록 처리
@Component
public class GrpcServerRunner implements ApplicationRunner, DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(GrpcServerRunner.class);

    @Autowired
    private Server grpcServer;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        grpcServer.start();

        logger.info("GrpcServer listening on port {}", grpcServer.getPort());

        grpcServer.awaitTermination();
    }
    
    @Override
    public void destroy() throws Exception {
        if (grpcServer != null) {
            grpcServer.shutdown();
        }
    }
}

deveely
Written by@deveely
자기실력이 좋다고 느껴지는건 공부를 안하고 있다는 신호

GitHubTistory