[java] Java에서 Protocol Buffers를 사용하여 원격 객체 호출 구현하기

Protocol Buffers는 구조화된 데이터 직렬화를 위한 프로토콜이다. 이를 사용하면 효율적이고 간결한 방식으로 데이터를 전달할 수 있다. Java에서는 Protocol Buffers를 사용하여 네트워크로부터 원격 객체 호출을 구현할 수 있다.

필요한 라이브러리 설정

먼저, Protocol Buffers를 사용하기 위해 Maven 또는 Gradle과 같은 빌드 도구를 사용하여 프로젝트에 필요한 라이브러리를 추가해야 한다.

Maven을 사용하는 경우

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.17.3</version>
</dependency>

Gradle을 사용하는 경우

dependencies {
    implementation 'com.google.protobuf:protobuf-java:3.17.3'
}

프로토콜 정의

Protocol Buffers는 .proto 파일을 사용하여 데이터 구조를 정의한다. 이를 통해 자동으로 Java 코드를 생성할 수 있다.

예를 들어, example.proto 파일을 생성하고 다음 코드를 작성한다.

syntax = "proto3";

package com.example;

message Request {
  string query = 1;
}

message Response {
  string result = 1;
}

service MyService {
  rpc Call(Request) returns (Response);
}

위 코드는 Request 메시지와 Response 메시지를 정의하고, MyService 서비스에는 Call 메소드가 있다는 것을 정의한다.

코드 생성

위에서 작성한 .proto 파일을 사용하여 Java 코드를 생성해야 한다. 프로젝트의 빌드 도구를 사용하여 코드를 생성할 수 있다.

Maven 사용 시

mvn protobuf:compile

Gradle 사용 시

gradle protobuf

이러한 명령을 실행하면 자동으로 .proto 파일에 정의된 메시지와 서비스에 대한 Java 코드가 생성된다.

클라이언트 구현

자동으로 생성된 Java 코드를 사용하여 클라이언트를 구현할 수 있다.

import com.example.MyService;
import com.example.Request;
import com.example.Response;

import java.util.concurrent.TimeUnit;

public class MyClient {
    private final MyService service;

    public MyClient(MyService service) {
        this.service = service;
    }

    public String callRemoteService(String query) {
        Request request = Request.newBuilder()
                .setQuery(query)
                .build();

        Response response = service.call(request);

        return response.getResult();
    }

    public static void main(String[] args) throws Exception {
        MyService service = MyService.newBlockingStub(channel);
        MyClient client = new MyClient(service);

        String result = client.callRemoteService("Hello, World!");
        System.out.println(result);
    }
}

위 코드에서는 자동으로 생성된 Java 코드를 사용하여 원격 서비스를 호출하고 결과를 받는 클라이언트를 구현한다. callRemoteService 메소드를 통해 원격 서비스를 호출하고, main 메소드에서 이를 실행한다.

서버 구현

서버 측에서도 Protocol Buffers를 사용하여 서비스를 구현할 수 있다.

import com.example.MyService;
import com.example.Request;
import com.example.Response;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;

public class MyServer {
    private final int port;
    private final Server server;

    public MyServer(int port) {
        this.port = port;
        this.server = ServerBuilder.forPort(port)
                .addService(new MyServiceImpl())
                .build();
    }

    public void start() throws IOException {
        server.start();
        System.out.println("Server started on port " + port);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Shutting down the server...");
            MyServer.this.stop();
        }));
    }

    public void stop() {
        server.shutdown();
    }

    private static class MyServiceImpl extends MyServiceGrpc.MyServiceImplBase {
        @Override
        public void call(Request request, StreamObserver<Response> responseObserver) {
            String query = request.getQuery();
            String result = "Response to " + query;

            Response response = Response.newBuilder()
                    .setResult(result)
                    .build();
            
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
    }

    public static void main(String[] args) throws Exception {
        MyServer server = new MyServer(50051);
        server.start();
    }
}

위 코드에서는 자동으로 생성된 Java 코드를 사용하여 서비스를 구현한다. MyServiceImpl 클래스에서 MyServiceGrpc.MyServiceImplBase를 상속하여 call 메소드를 오버라이드해서 구현한다.

실행

클라이언트와 서버의 구현이 모두 완료되었다면, 다음 단계로 넘어가 실행해보자.

  1. 서버를 실행한다.
  2. 클라이언트를 실행하여 서버로부터 결과를 받는다.
  3. 클라이언트에서 받은 결과를 확인한다.

결론

Java에서 Protocol Buffers를 사용하여 원격 객체 호출을 구현하는 방법에 대해 알아보았다. 이를 통해 데이터 구조화와 네트워크 통신을 효율적으로 처리할 수 있다. Protocol Buffers를 사용하면 서비스 간의 통신 구현이 용이해지며, 코드의 가독성과 성능을 향상시킬 수 있다.


참고 자료: