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

Protocol Buffers는 구조화된 데이터를 직렬화하여 다른 시스템 사이에서 효율적인 통신을 가능하게 하는 고성능의 이진 데이터 형식입니다. Java에서 Protocol Buffers를 사용하여 원격 호출을 구현하는 방법에 대해 알아보겠습니다.

1. Protocol Buffers 설치

먼저, Protocol Buffers를 사용하기 위해 프로토콜 버퍼 컴파일러(protoc)를 설치해야 합니다. 이 컴파일러는 .proto 파일을 컴파일하여 Java 클래스를 생성합니다.

Windows 사용자는 공식 Protocol Buffers 웹사이트에서 protoc 컴파일러를 다운로드하여 설치하고, Linux나 macOS 사용자는 해당 시스템 패키지 매니저를 사용하여 설치합니다.

2. Protocol Buffers 정의 파일 작성

다음으로, RPC(원격 프로시저 호출)를 사용하여 원격 호출을 구현할 서비스를 정의하는 .proto 파일을 작성해야 합니다. 이 예제에서는 calculator.proto라는 파일을 작성합니다.

syntax = "proto3";

package calculator;

service CalculatorService {
  rpc Add(AddRequest) returns (AddResponse);
}

message AddRequest {
  int32 operand1 = 1;
  int32 operand2 = 2;
}

message AddResponse {
  int32 result = 1;
}

.proto 파일은 package, service, message 등의 섹션으로 구성되며, 각 섹션은 Protocol Buffers에서 사용되는 일종의 DSL(Domain Specific Language)로 작성됩니다. 위의 예제에서는 CalculatorService라는 서비스와 AddRequest, AddResponse라는 메시지를 정의하였습니다.

3. Protocol Buffers 컴파일

.proto 파일을 작성했다면, protoc 컴파일러를 사용하여 Java 클래스를 생성해야 합니다. 컴파일 명령은 다음과 같습니다.

protoc --java_out=./generated/ calculator.proto

위의 명령을 실행하면 calculator.proto 파일에 정의된 서비스와 메시지에 대한 Java 클래스가 ./generated/ 디렉토리에 생성됩니다.

4. Server 구현

Protocol Buffers를 사용하여 원격 호출을 구현하기 위해서는 서버와 클라이언트를 모두 구현해야 합니다. 먼저 서버 측을 구현해 보도록 하겠습니다.

import calculator.CalculatorService;
import calculator.AddRequest;
import calculator.AddResponse;

import java.io.IOException;

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

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

    public CalculatorServer(int port) throws IOException {
        this.port = port;
        this.server = ServerBuilder.forPort(port)
            .addService(new CalculatorServiceImpl())
            .build();
    }

    public void start() throws IOException {
        server.start();
        System.out.println("Server started on port " + port);
    }

    public void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    private static class CalculatorServiceImpl extends CalculatorServiceGrpc.CalculatorServiceImplBase {
        @Override
        public void add(AddRequest request, StreamObserver<AddResponse> responseObserver) {
            int result = request.getOperand1() + request.getOperand2();
            AddResponse response = AddResponse.newBuilder().setResult(result).build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        CalculatorServer server = new CalculatorServer(50051);
        server.start();
        server.blockUntilShutdown();
    }
}

위의 예제는 CalculatorService를 구현한 CalculatorServiceImpl 클래스를 작성한 뒤, 해당 클래스를 서버에 추가하고 서버를 시작하는 코드입니다. CalculatorServiceImpl 클래스의 add 메소드에서는 AddRequest로부터 두 개의 피연산자를 받아 더한 후, AddResponse에 결과를 담아 클라이언트로 전송하는 간단한 예제입니다.

5. Client 구현

서버 측을 구현했으니 이제 클라이언트를 구현해보겠습니다.

import calculator.CalculateRequest;
import calculator.CalculateResponse;
import calculator.CalculatorServiceGrpc;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

public class CalculatorClient {
    private final ManagedChannel channel;
    private final CalculatorServiceGrpc.CalculatorServiceBlockingStub blockingStub;
    private final CalculatorServiceGrpc.CalculatorServiceStub asyncStub;

    public CalculatorClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port)
            .usePlaintext()
            .build();
        blockingStub = CalculatorServiceGrpc.newBlockingStub(channel);
        asyncStub = CalculatorServiceGrpc.newStub(channel);
    }

    public void add(int operand1, int operand2) {
        CalculateRequest request = CalculateRequest.newBuilder()
            .setOperand1(operand1)
            .setOperand2(operand2)
            .build();
        
        CalculateResponse response = blockingStub.add(request);
        System.out.println("Result: " + response.getResult());
    }

    public static void main(String[] args) {
        CalculatorClient client = new CalculatorClient("localhost", 50051);
        client.add(10, 20);
    }
}

위의 예제는 CalculatorService를 호출하는 CalculatorClient 클래스를 작성한 뒤, add 메소드를 호출하여 서버에게 두 개의 피연산자를 전달하고 결과를 받는 예제입니다.

결론

이제 Protocol Buffers를 사용하여 Java에서 원격 호출을 구현하는 방법을 알아보았습니다. Protocol Buffers는 간단한 구조화된 데이터 형식이지만, 효율적인 통신을 위해 다양한 기능을 제공합니다. Java에서는 Protocol Buffers를 사용하여 서버와 클라이언트 간의 효율적인 통신을 구현할 수 있습니다.