Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KMP와 kotlinx.rpc로 서버와 클라이언트 동기화

KwakEuiJin
December 20, 2024

KMP와 kotlinx.rpc로 서버와 클라이언트 동기화

KwakEuiJin

December 20, 2024
Tweet

More Decks by KwakEuiJin

Other Decks in Programming

Transcript

  1. KMP - Common Multiplatform 좋은 API Design이란? kotlinx.rpc 소개 KMP와

    kotlinx.rpc로 프로젝트 구현하기 Kotlin Multiplatform 그 미래는? 목차
  2. Kotlin Multiplatform 이란? Kotlin Multiplatform이란? 여러 플랫폼(Android, iOS, 웹, 데스크탑

    등)에서 공통 코드를 공유하면서 각 플랫폼의 네이티브 기능을 유지할 수 있는 기술 특징: • 코드 재사용성 증가 • 네이티브 성능 유지 • 플랫폼별 커스터마이징 가능 Kotlin Multiplatform
  3. Android Model Class Repository Client Shared Server iOS Web Application

    이전 발표에 대해서 Integration Test Model Class Shared
  4. 클라이언트가 API Design을 왜? 문득 보시자마자 의아하신 분들도 있을 겁니다.

    왜 클라이언트 개발자가 API Design(설계)까지 신경써야해?
  5. 서버 통신 개발 시 필요한 데이터가 누락되거나 혹은 필요하지 않은

    데이터까지 내려주어 혼동이 있는 경우 나 이 화면 그리려면 이 데이터가 필요해! 앗 그러면 추가작업이 필요해! Server Client 개발할 때 가장 불편했던 점
  6. 서버 통신 개발 시 필요한 데이터가 누락되거나 혹은 필요하지 않은

    데이터까지 내려주어 혼동이 있는 경우 나 이 화면 그리려면 이 데이터가 필요해! 앗 그러면 추가작업이 필요해! Server Client 개발할 때 가장 불편했던 점 아니 이 데이터는 필요 없는 데이터야!
  7. 유지보수 진행 시 정의된 스펙이 중간에 변경되거나 정확하게 전달되지 않은

    경우 문서 상 스펙은 이거라고 했는데?! 받아줘! 그거 아닌데 Server Client 개발할 때 가장 불편했던 점
  8. Android 개발에서 협업을 경험하며 ▪ 필요 유무에 따른 데이터 포맷

    변경 요청으로 개발 생산성 하락 ▪ 유지 보수 진행 시 서버·웹 과의 인터페이스 의도 파악 어려움 ▪ 요청 / 응답 모델 부정확성으로 인한 빈번한 오류 발생 ▪ 노후화된 API 문서로 인한 개발 장애 ▪ 복잡한 Path 관리
  9. // Ktor를 활용한 클라이언트 -> 서버 호출 코드 fun getHttpClient(host:

    String): HttpClient { return HttpClient { fun createHttpClient() = HttpClient { install(ContentNegotiation) { json(Json { . . . }) } } suspend fun getAllTasks(): List<Task> { return httpClient.get("tasks").body() } 하지만 우리의 현실은?? 만약에 해당 코드를 개발하다가. . . ▪ “tasks”와 같은 Path를 잘못 적었다면? ▪ 갑작스럽게 Query가 추가되었다면? ▪ API 응답 구조가 갑자기 변경되었다면? 결과적으로 ➔ 예상치 못한 에러가 해당 API를 실제로 호출했을 때야 알게 될 것임 ➔ 실질적으로 매번 모든 테스트 코드로 커버하는 것은 거의 불가능
  10. // Shared 모듈 Data Model @Serializable data class Task( val

    name: String, val description: String, val priority: Priority ) // Client Task 리소스 요청 로직 suspend fun getAllTasks(): List<Task> { return httpClient.get("tasks").body() } // Server Task 객체 반환 로직 interface TaskRepository { fun allTasks(): List<Task> } Build Success
  11. // Shared 모듈 Data Model @Serializable data class Task( val

    name: String, val priority: Priority, ) // Client Task 리소스 요청 로직 suspend fun getAllTasks(): List<Task> { return httpClient.get("tasks").body() } // Server Task 객체 반환 로직 interface TaskRepository { fun allTasks(): List<Task> } Build Failed: description이 없어요!
  12. ▪ 필요 유무에 따른 데이터 포맷 변경 요청으로 개발 생산성

    하락 ▪ 유지 보수 진행 시 서버·웹 과의 인터페이스 의도 파악 어려움 ▪ 요청 / 응답 모델 부정확성으로 인한 빈번한 오류 발생 ▪ 노후화된 API 문서로 인한 개발 장애 ▪ 복잡한 Path 관리 Android 개발에서 협업을 경험하며
  13. Android Model Class Repository Client Shared Server iOS Web Application

    공통 모듈이 있다면? 서버의 모델을 클라이언트와 공유함으로써 모델 변경 시 클라이언트가 즉각적으로 대응할 수 있었습니다.
  14. Android Model Class Repository Client Shared Server iOS Web Application

    공통 모듈이 있다면? 그렇다면 이를 힌트로 데이터 모델 뿐 아니라 함수도 공유한다면 컴파일 타임에 서버의 모든 변경사항을 알 수 있지 않을까?
  15. REST API 의 구조적 한계: 서버와 클라이언트 간 동기화 문제

    Q: 왜 REST API는 서버와 클라이언트 간의 동기화가 힘든가요?? A: 리소스 중심 설계(Resource-oriented) 때문에 리소스 중심 설계는 아래와 같은 문제를 야기함 1. 문서화(Documentation)에 대한 의존 2. 런타임 시점 오류 감지
  16. 리소스 중심 설계(Resource-oriented) ▪ REST API는 리소스(URL)와 HTTP 메서드(GET, POST,

    PUT, DELETE) 조합을 통해 기능을 노출 ▪ 상태 전이(State Transfer) 중심의 설계 ▪ 즉 REST API는 HTTP 메서드와 URL 조합으로 리소스를 노출하는 방식으로 함수 호출 단위의 상호작용을 지원하지 못함
  17. ▪ 리소스 중심의 설계이기 때문에 REST API 계약은 Swagger /

    Notion같은 문서로 정의, 관리 ▪ 문서가 최신 상태를 유지하지 않으면 클라이언트와 서버 간 불일치 발생 ▪ 런타임에서 요청 실패나 스키마 불일치를 감지 가능 → 개발 속도와 안정성 저하 문서화(Documentation)에 대한 의존 궁극적으로 서버 변경 사항 즉각 반영 어려움 및 컴파일 타임 타입 안정성 확보 불가능
  18. REST는 문서에 의존하는 계약 인터페이스 공유의 부재 ▪ REST는 리소스

    중심의 설계로 함수 자체의 공유와 직접 연결되지 않음. ▪ 언어 수준에서 클라이언트와 서버가 동일한 인터페이스를 공유하지 않음 ▪ 데이터 구조와 요청 로직을 별도로 구현해야 하므로 개발 비용 증가 그래서 이 문제를 어떻게 해결할건데.. A: 그냥 매번 런타임에 호출해보자. . .? B: 아니 그냥 테스트 코드 매번 짜면 되잖아? C: React에 서버 액션 같은 건 어때? D: grpc 같은 함수를 실제 호출하는 서버 아키텍쳐도 있어!
  19. Call calculateQuizScore(quiz) on QuizSystem Excute calculateQuizScore with quiz on QuizSystem

    RPC(Remote Procedure Call) RPC Request { calculateQuizScore(quiz), QuizSystem } MainSystem
  20. Call calculateQuizScore(quiz) on QuizSystem Excute calculateQuizScore with quiz on QuizSystem

    RPC(Remote Procedure Call) RPC Request { calculateQuizScore(quiz), QuizSystem } RPC Response MainSystem
  21. class QuizService { private val quizSystem: QuizSystem = getQuizSystem() suspend

    fun calculateQuizScore(quiz: Quiz) { quizSystem.calculateQuizScore(quiz) // process quiz score } } MainSystem
  22. Google RPC(gRPC) ▪ Google이 개발한 오픈소스 RPC 프레임워크. ▪ HTTP/2

    기반 설계로 빠르고 효율적인 통신 가능 ▪ Protobuf로 인터페이스를 정의하며 IDL 제공 ▪ 양방향 스트리밍 지원 A high performance, open source universal RPC framework
  23. ▪ IDL 의존성: Protobuf 사용 필수, Proto 파일 생성 필수

    ▪ 설정 복잡성: Protobuf 컴파일러 필요, 추가 설정 및 바이너리 관리 필요 ▪ 멀티플랫폼 지원 부족: JVM 외의 플랫폼에서 제약 ▪ 런타임 의존성: 런타임에서만 에러를 발견할 수 있어 컴파일 타임 안전성 부족 Google RPC(gRPC)는 아니야! 즉 기존 REST API의 문제를 해결하기에는 부족
  24. 공통 인터페이스 기반 설계 ▪ 전통적인 RPC 시스템은 IDL을 사용하지만,

    kotlinx.rpc는 Kotlin의 인터페이스 자체가 그 역할을 수행 ▪ 결과적으로 서버와 클라이언트가 동일한 인터페이스를 공유 가능 자동화된 Stub 생성 ▪ 클라이언트와 서버 간 통신을 위해 Stub(클라이언트 측 프록시) 자동 생성 ▪ 직접 네트워크 코드를 작성할 필요 없이 함수 호출만으로 서버 통신 수행 Kotlinx.serialization 통합 ▪ RPC를 위해 데이터를 직렬화 및 역직렬화할 때 Kotlinx.serialization 활용 ▪ JSON, Protobuf(experimental) 등 다양한 포맷 지원 Kotlinx RPC(Remote Procedure Call) 는?
  25. @Rpc interface QuizService : RemoteService { suspend fun getQuiz(): List<Quiz>

    suspend fun calculateQuizScore(answers: List<Int>): QuizResult kotlinx.rpc 서비스 통신 구조 Service Declaration
  26. Service Declaration Service Stub Service Implementation val service = rpcClient.withService<QuizService>()

    class QuizServiceImpl(override val coroutineContext:CoroutineContext) : QuizService { // implementation } kotlinx.rpc 서비스 통신 구조
  27. Service Declaration Service Stub Service Implementation val service = rpcClient.withService<QuizService>()

    class QuizServiceImpl(override val coroutineContext:CoroutineContext) : QuizService { // implementation } kotlinx.rpc 서비스 통신 구조 Client Server shared
  28. Service Declaration Service Stub Service Implementation RPC Client RPC Server

    kotlinx.rpc 서비스 통신 구조 네트워크 세부사항
  29. RPC Client RPC Server WebSockets gRPC HTTP . . .

    kotlinx.rpc 서비스 통신 구조
  30. RPC 서비스\ @Rpc interface QuizService : RemoteService { suspend fun

    getQuiz(): List<Quiz> suspend fun calculateQuizScore(answers: List<Int>): QuizResult } @Serializable data class Quiz( val question: String, val options: List<String>, val correctIndex: Int ) @Serializable data class QuizResult( val correctAnswers: Int ) Shared Module - 공통 인터페이스 정의 공통 Data Model Class
  31. RPC 서비스\ @Rpc interface QuizService : RemoteService { suspend fun

    getQuiz(): List<Quiz> suspend fun calculateQuizScore(answers: List<Int>): QuizResult } @Serializable data class Quiz( val question: String, val options: List<String>, val correctIndex: Int ) @Serializable data class QuizResult( val correctAnswers: Int ) Shared Module - 공통 인터페이스 정의
  32. RPC 서비스\ @Rpc interface QuizService : RemoteService { suspend fun

    getQuiz(): List<Quiz> suspend fun calculateQuizScore(answers: List<Int>): QuizResult } @Serializable data class Quiz( val question: String, val options: List<String>, val correctIndex: Int ) @Serializable data class QuizResult( val correctAnswers: Int ) Shared Module - 공통 인터페이스 정의
  33. RPC 서비스\ class QuizServiceImpl( override val coroutineContext: CoroutineContext ) :

    QuizService { override suspend fun getQuiz(): List<Quiz> = quizMockList // mock quiz list 객체 override suspend fun calculateQuizScore(answerIndexes: List<Int>): QuizResult { Calculate Quiz Score Implementation . . . } } Server Module – RPC 서버 구현체
  34. RPC 서비스\ fun Application.configureRPC() { install(RPC) routing { rpc("/quiz") {

    rpcConfig { serialization { json(Json { prettyPrint = true }) } } registerService<QuizService> { coroutineContext -> QuizServiceImpl(coroutineContext) } } } } Server Module – RPC 서버 등록 ▪ registerService 를 활용하여 서비스의 구현체를 등록 ▪ 서버는 단순히 인터페이스를 구현하고 구현체를 RPC에 등록 ▪ 네트워크 요청과 데이터 직렬화 과정은 kotlinx.rpc가 자동으로 처리
  35. RPC 서비스\ val rpcClient = HttpClient { installRPC() }.rpc {

    rpcConfig { serialization { json() } } } val quizService = rpcClient.withService<QuizService>() val quizzes = quizService.getwithServiceQuiz() Client Module – RPC 클라이언트 호출 ▪ withService 를 활용하여 서버에서 제공하는 서비스의 Stub 객체를 생성 ▪ 클라이언트는 이 Stub를 사용하여 RPC 요청을 마치 로컬 함수처럼 호출 ▪ 네트워크 요청과 데이터 직렬화 과정은 kotlinx.rpc가 자동으로 처리
  36. 즉 kotlinx.rpc 는 함수 호출 형태로 네트워크 통신을 추상화 함

    장점 1. 타입 안정성: 실제 같은 인터페이스를 호출하고 구현하기 때문에 컴파일 타임에 서버 – 클라이언트 간의 불일치를 검사 가능 2. 코드 일관성: 서버와 클라이언트가 동일한 모듈의 인터페이스를 공유하므로 변경 사항이 즉시 반영 3. 협업 효율성: 문서화(Swagger/OpenAPI)에 의존하지 않고, 코드 자체가 업데이트 되기 때문에 실시간 동기화 문서 역할을 수행 가능 Kotlinx.rpc를 REST의 한계 뛰어넘기
  37. ▪ 필요 유무에 따른 데이터 포맷 변경 요청으로 개발 생산성

    하락 ▪ 유지 보수 진행 시 서버·웹 과의 인터페이스 의도 파악 어려움 ▪ 요청 / 응답 모델 부정확성으로 인한 빈번한 오류 발생 ▪ 노후화된 API 문서로 인한 개발 장애 ▪ 복잡한 Path 관리 Kotlinx.rpc를 REST의 한계 뛰어넘기
  38. 1. 성능 이슈 • 메모리 누수 발생 (Args/Response 객체의 해제

    문제) • 장기 실행 시 메모리 부족 가능 2. 에러 처리의 복잡성 • 코루틴 컨텍스트 상실 및 WebSocket을 전송 계층으로 활용하기에 디버깅 및 에러 추적 어려움 3. 성숙도 및 안정성(version 0.4.0) • 초기 개발 단계로 일부 기능 미완성 • 프로덕션 환경 사용 시 추가 검토 필요 Kotlinx.rpc의 한계와 단점 아직은 좀… 이른 것 같아요 하지만 ‘아직’ 이란 건 굉장한 잠재력을 가졌다는 것!
  39. Kotlin Multiplatform의 성장 Kotlin 생태계와 Multiplatform 환경의 발전 Kotlin Multiplatform

    프로젝트는 점점 더 많은 라이브러리를 통해 지원받고 있음 https://klibs.io/
  40. Kotlin Multiplatform의 성장 Safari version 18.2 부터 Wasm GC 추가

    Safari Web Browser 에서 CMP Wasm의 문제가 해결되었으며 코틀린/wasm이 모든 브라우저에서 다 돌아가게 되었음!
  41. Kotlin Multiplatform 개발에 대한 개인적인 결론은? ▪ 개인적으로 생각할 때

    KMP에 대한 공식적인 지원이 지속된다면 미래는 굉장히 유망할 것이라고 생각 ▪ 무엇보다도 안드로이드 개발자인 내가 웹, 서버를 쉽게 개발해 볼 수 있는 기회가 주어진 것 자체에 굉장히 많은 흥미를 느낌. ▪ 다만 올 플랫폼 플레이어로 뛰기에는 너무나 많은 지식이 필요하기에 Kotlin Multiplatform을 활용하여 공통된 로직을 분리하고 공유할 수 있는 매커니즘을 잘 활용해야 함 ▪ 또한 앞서 얘기한 성능 및 안정성이 해결되고 적절하게 역할을 분배하여 잘 활용한다면 이만큼 매력적인 기술을 찾아보기는 힘들 것 같다고 생각
  42. 더 많은 것을 배우고 싶다면? ▪ ktor – Document https://ktor.io/docs/welcome.html

    ▪ ktor-samples https://github.com/ktorio/ktor-samples ▪ ktor-rpc https://kotlin.github.io/kotlinx-rpc/get-started.html ▪ First steps with Kotlin RPC with Kotlin Multiplatform https://ktor.io/docs/tutorial-first-steps-with-kotlin- rpc.html ▪ Kotlin Multiplatform and Ktor: Developing Client and Server Simultaneously with Integration Testing https://github.com/kez-lab/KotlinRPCSample