[iOS] gRPC를 이용한 데이터 공유 및 Proto 변환 과정

pola·2024년 11월 14일

Company

목록 보기
2/7

이번 프로젝트에서는 로컬에서 저장된 데이터를 서버에 전달하는 작업을 진행함!
Android와 동일한 데이터를 공유하기 위해 gRPC를 사용해 데이터 모델을 맞춘기로 했다고..

gRPC란?

  • Google에서 개발한 오픈 소스 고성능 RPC(Remote Procedure Call) 프레임워크
  • 다양한 환경에서 실행할 수 있으며 Protocol Buffer를 사용해 직렬화된 데이터 구조로 데이터를 전송

Proto 변환 과정

처음 인수인계를 받을 때, 프로젝트의 .proto 파일이 작성되어 있어, 기존 내용을 유지하며 변환 과정을 진행

그러나 경로 문제로 변환에 어려움을 겪었고, 다른 개발자들이 최상위 폴더에 proto를 지정한 뒤 변환 작업을 진행하는 것을 참고해, 최상위 폴더에 protoGenerated 폴더를 추가해 변환 파일을 저장하도록 설정

폴더 구조

  • Proto: proto폴더, protoGenerated폴더, proto 변환 쉘
  • proto: .proto 파일들
  • protoGenerated: 변환된 Swift 파일, grpc 파일들

기존 쉘 스크립트

기존에는 Proto 폴더 안에서 바로 변환을 진행

arr=$(find ./proto -name '*.proto')

for file in $arr
do
    protoc -I=. --swift_out=. --grpc-swift_out=. $file
    echo "$file to Swift..."
done
echo "ProtoToSwift Complete"

수정된 쉘 스크립트

  • 새로운 스크립트에서는 proto 디렉터리를 import 경로로 설정하고, 변환된 파일을 protoGenerated에 저장하도록 수정
arr=$(find ./proto -name '*.proto')

for file in $arr
do
        protoc -I=./proto --swift_out=./protoGenerated --grpc-swift_out=./protoGenerated $file
	echo "$file to Swift..."
done
echo "ProtoToSwift Complete"

주의사항: Swift Prefix

  • proto 파일에 option swift_prefix를 설정하지 않으면 패키지에 따라 언더스코어가 붙음..
  • Swift Protocol Buffer 문서에서는 typealias를 활용할 것을 권장하고 있어, 추후 이를 반영해 수정할 예정

gRPC 서비스 구성

이번 프로젝트에서는 gRPC 호출을 효율적으로 처리하기 위해 GrpcServiceHelper, GrpcRepository, GrpcRepositoryInterface, GrpcServiceUseCase와 같은 구조를 도입

GrpcServiceHelper

  • gRPC 호출을 수행하며, 공통 로직을 모듈화
import GRPC
import NIO
import RxSwift
import SwiftProtobuf

final class GrpcServiceHelper {
    
    private let group: EventLoopGroup
    
    init(group: EventLoopGroup) {
        self.group = group
    }
    
    func performGrpcCall<Request: SwiftProtobuf.Message, Response: SwiftProtobuf.Message>(
        _ request: Request,
        callMethod: @escaping (Request) -> UnaryCall<Request, Response>
    ) -> Single<Response> {
        return Single.create { single in
            let call = callMethod(request)
            
            call.response.whenComplete { result in
                switch result {
                case .success(let response):
                    single(.success(response))
                case .failure(let error):
                    single(.failure(error))
                }
            }
            
            return Disposables.create {
                try? self.group.syncShutdownGracefully()
            }
        }
    }

GrpcRepository

  • GrpcServiceHelper를 활용해 GrpcRepository에서 gRPC 호출을 처리
import GRPC
import NIO
import RxSwift

final class GrpcRepository: GrpcRepositoryInterface {
    typealias ServiceNIOClient = AAA_ServiceNIOClient
    
    private let serviceClient: ServiceNIOClient
    private let helper: GrpcServiceHelper
    
    override init() {
        let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        self.helper = GrpcServiceHelper(group: group)
        
        let channel = ClientConnection.insecure(group: group)
            .connect(host: "host", port: 1111)
        self.serviceClient = AAA_ServiceNIOClient(channel: channel)
    }
    
    func createServic(aa: String) -> Single<CreateResponse> {
        var request = CreatRequest()
        request.aa = aa
        return helper.performGrpcCall(request) { self.serviceClient.create($0) }
    }
}

GrpcRepositoryInterface

  • 인터페이스를 통해 GrpcRepository의 기능을 추상화
protocol GrpcServiceRepositoryInterface {
    func createServic(aa: String) -> Single<CreateResponse>
}

GrpcServiceUseCase

  • Usecase에서 GrpcRepositoryInterface를 활용하여 로직을 처리
import RxSwift

final class GrpcNoteServiceUseCase {
    private let grpcServiceRepositoryInterface: GrpcServiceRepositoryInterface!
    
    init(grpcServiceRepositoryInterface: GrpcServiceRepositoryInterface!) {
        self.grpcServiceRepositoryInterface = grpcServiceRepositoryInterface
    }
    
    func createExecute(aa: String) -> Single<CreateResponse> {
        return grpcServiceRepositoryInterface.createServic(aa: aa)
    }
    
}

이제 ViewModel에서 GrpcServiceUseCase를 생성하여 사용할 수 있음!

정보가 많이 없어 gRPC와 Proto 변환 및 구현 과정을 정리해봄!

profile
pola

0개의 댓글