MSA구조에서 Spring Service간 통신에 REST를 써도되지만 gRPC를 쓸 수도 있다.

이경우 설정방법에 대해서 경험한 바를 여기 적는다.

 

pom.xml 에 의존성 추가

서버/클라이언트 공통

아래 내용을 서버/클라이언트 pom.xml에 공히 추가하면 되고,

src/main/kotlin/이냐 src/main/java/냐 이부분만 서로 다르게 수정해주면 된다.

protobuf-javacom.google.protobuf:protoc:3.12.4 이부분등 버전을 맞춰주지 않으면 빌드과정에서 오류가 나는 경우가 있었으니 주의.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <dependencies>

        <!-- gRPC -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.12.4</version>  <!--이 버전을 아래쪽 ptoroc버전과 맞춰야 함-->
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.41.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.41.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.41.0</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <!--Maven 빌드 과정 중에 운영 체제(OS)에 관한 정보를 제공하고 설정하는 데 도움을 줍니다. -->
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.0</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.12.4:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.51.0:exe:${os.detected.classifier}</pluginArtifact>
                    <outputBaseDirectory>src/main/kotlin/</outputBaseDirectory>
                    <outputDirectory>src/main/kotlin/</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                        <configuration>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

 

클라이언트 전용

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-client-spring-boot-autoconfigure</artifactId>
    <version>2.15.0.RELEASE</version>
</dependency>

위의 내용은 필수는 아니나 코드가 좀 더 깔끔해지게 도와준다.

 

protobuf 파일추가

아래와 같이 .proto 파일을 제작해서 src/main/proto 폴더안에 둔다(다른곳에 두어도 되며, 프로젝트간 공용위치에 두어도 된다)

//session_service.proto
// Version: 1.0.0

syntax = "proto3";

package com.sevity.authservice.grpc;

service SesseionService {
    rpc GetUserId (SessionRequest) returns (UserResponse) {};
}

message SessionRequest {
    string sessionId = 1;
}

message UserResponse {
    int32 userId = 1;
}

중요한건 아래 트러블슈팅에서도 나오지만 package명을 서버/클라이언트가 다르게 하면 못찾는다는 오류가 떴다.

 

빌드

mvn clean install 등을 수행하면 .proto 파일을 컴파일해서 다음과 같은 2개의 java파일을 자동으로 생성해준다.

위치또한 pom.xml파일에 지정된 대로 생성된다.

파일2개중 하나만 생성된적도 있었는데 여기보고 해결했던것 같다.

protoc를 터미널에서 직접 사용해서 .proto파일을 컴파일하는 것도 가능하긴하지만, mvn에 통합해서 운용하는게 훨씬 편하고, protoc 사용과정에서 직접빌드해야하는등 우여곡절도 발생했다.

서버코드 제작

아래처럼 생성된 java파일들 (grpc패키지)을 import해주고,

gRPC서버측 구현을 해준다(SessionService의 getUserId 함수)

package com.sevity.authservice.service;

import com.sevity.authservice.grpc.SesseionServiceGrpc;
import com.sevity.authservice.grpc.SessionService.SessionRequest;
import com.sevity.authservice.grpc.SessionService.UserResponse;

import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import io.grpc.Status;


@Service
public class SessionServiceImpl extends SesseionServiceGrpc.SesseionServiceImplBase {
    @Override
    public void getUserId(SessionRequest request, StreamObserver<UserResponse> responseObserver) {
        String sessionId = request.getSessionId();

        UserResponse response = UserResponse.newBuilder().setUserId(sessionId).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

 

application.properties에서 포트설정해주고(이때 src/main/resources뿐 아니라 src/test/resources에 있는 파일도 해줘야함에 주의)

# in application.properties
grpc.server.port = 50051

 

다음처럼 gRPC서버 띄욱 listen작업도 해줘야 했다.

package com.sevity.authservice.config;

import ...
@Configuration
public class GrpcServerConfig {
    private static final Logger logger = LoggerFactory.getLogger(GrpcServerConfig.class);

    @Autowired
    private SessionServiceImpl sessionService;
    
    private Server server;
    
    @Value("${grpc.server.port}")
    private int port;

    @PostConstruct
    public void startServer() throws IOException {
        server = ServerBuilder
            .forPort(port)
            .addService(sessionService)  // Your gRPC service implementation
            .build();

        server.start();
        logger.info("sevity gRPC server started on port {}", port);
        logger.info("sevity gRPC service name: {}", sessionService.getClass().getSimpleName());        
    }

    @PreDestroy
    public void stopServer() {
        if (server != null) {
            server.shutdown();
        }
        logger.info("sevity gRPC server stopped");
    }
}

 

클라이언트코드 제작

application.properties에 아래줄 추가

# GRPC
grpc.client.authService.address=static://sevity.com:50051
grpc.client.authService.negotiationType=PLAINTEXT

 

빌드

maven,kotlin환경이었는데, 빌드과정은 서버측과 큰차이가 없다.(같은 방법으로 빌드하면 된다)

(생성되는 파일도 여전히 .java이며 kotlin과 통합에 문제가 없었다)

 

호출하는 코드는 아래와 같다(코틀린임에 주의)

/submit 매핑과, cookie, session관련 처리때문에 복잡하나 그 부분은 제외하고 gRPC stub에 대한 내용만 눈여겨보자.

(서버측에 비해서 필요할때 문맥중간에서 요청하게된다)

@RestController
class SubmissionController {

    @GrpcClient("authService")
    private lateinit var sessionServiceStub: SesseionServiceGrpc.SesseionServiceBlockingStub

    @PostMapping("/submit")
    fun submitCode(request: HttpServletRequest): ResponseEntity<String> {
        val cookies = request.cookies
        val sessionId = cookies?.find { it.name == "SESSION" }?.value
            ?: return ResponseEntity("Session ID not found", HttpStatus.UNAUTHORIZED)

        val request2 = SessionRequest.newBuilder().setSessionId(sessionId).build()
        try {
            val response = sessionServiceStub.getUserId(request2)
            println("Received user ID: ${response.userId}")
            return ResponseEntity("Code submitted", HttpStatus.OK)
        } catch (e: InvocationTargetException) {
            e.targetException.printStackTrace()
        }

        // 나머지 로직
        return ResponseEntity("Code submitted", HttpStatus.OK)
    }
}

 

 

트러블슈팅

io.grpc.StatusRuntimeException: UNIMPLEMENTED: Method not found

.proto 파일은 파일내 package경로까지 완전히 동일한 파일을 사용하지 않으면 못찾는다고 에러가 났다.

이거때문에 한참헤멤 ㅠ 여기참조. 유일한 솔루션인지는 잘 모르겠으나,

서버기준으로 package명까지 동일하게 맞춰주니 해결됨.

 

 

반응형

+ Recent posts