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

Introduction to kotlinx.rpc

Arawn Park
February 14, 2025

Introduction to kotlinx.rpc

kotlinx.rpc는 JetBrains가 개발한 Kotlin 멀티플랫폼 RPC 라이브러리로 클라이언트와 서버 모두에서 Kotlin 언어의 표준 기능을 활용하여 원격 함수를 호출할 수 있도록 지원합니다. 이 덕분에 개발자는 네트워크 프로그래밍의 복잡한 세부사항을 몰라도, 마치 로컬 함수처럼 원격 서비스를 호출할 수 있습니다.

kotlinx.rpc is a multiplatform Kotlin library that provides its users with tools to perform Remote Procedure Calls using Kotlin language constructs with as easy setup as possible.

Arawn Park

February 14, 2025
Tweet

More Decks by Arawn Park

Other Decks in Programming

Transcript

  1. kotlinx.rpc is a multiplatform Kotlin library that provides its users

    with tools to perform Remote Procedure Calls using Kotlin language constructs with as easy setup as possible. 박용권 당근마켓
  2. 31$5FDIOPMPHJFT gRPC Apache Thrift Apache Dubbo SOAP Simple Object Access

    Protocol WCF Windows Communication Foundation JSON RPC / XML RPC Java RMI Remote Method Invocation ZeroC; IceRPC
  3. 4FSWJDF%FDMBSBUJPOVTJOH1SPUPDPMCVGGFS syntax = "proto3"; package coffeehouse.contract.order; service OrderService { rpc

    PlaceOrder (PlaceOrderRequest) returns (PlaceOrderResponse); } message PlaceOrderRequest { string customer_id = 1; repeated OrderLine order_lines = 2; } message PlaceOrderResponse { string order_id = 1; } message OrderLine { string product_code = 1; int32 quantity = 2; }
  4. 4FSWJDF*NQMFNFOUBUJPOXJUI,PUMJOBOEH31$ import coffeehouse.contract.order.Order.PlaceOrderRequest import coffeehouse.contract.order.Order.PlaceOrderResponse import coffeehouse.contract.order.OrderServiceGrpcKt import io.grpc.ServerBuilder class

    OrderServiceImpl : OrderServiceGrpcKt.OrderServiceCoroutineImplBase() { override suspend fun placeOrder(request: PlaceOrderRequest): PlaceOrderResponse { val orderId = UUID.randomUUID().toString() println("! ઱ޙ ࢤࢿ: ҊёID=${request.customerId}, ઱ޙID=$orderId, ઱ޙ ݾ۾=${request.orderLinesList}") return PlaceOrderResponse.newBuilder().setOrderId(orderId).build() } } fun main() = runBlocking { val server = ServerBuilder .forPort(50051) .addService(OrderServiceImpl()) .build() .start() println("✅ gRPC ழೖ ઱ޙ ࢲߡ प೯ ઺... (ನ౟ 50051)") server.awaitTermination() }
  5. 4FSWJDF4UVQVTJOH+BWBBOEH31$ import coffeehouse.contract.order.Order; import coffeehouse.contract.order.OrderServiceGrpc; import io.grpc.ManagedChannelBuilder; void main(String[] args)

    { var channel = ManagedChannelBuilder.forAddress("localhost", 50051).usePlaintext().build(); var stub = OrderServiceGrpc.newBlockingStub(channel); var request = Order.PlaceOrderRequest.newBuilder() .setCustomerId("java-user-1234") .addOrderLines(Order.OrderLine.newBuilder().setProductCode("ESPRESSO").setQuantity(2).build()) .build(); var response = stub.placeOrder(request); try { System.out.println("✅ Java ௿ۄ੉঱౟ - ઱ޙ ৮ܐ, ઱ޙ ID: " + response.getOrderId()); } catch (Exception error) { System.out.println("✅ Java ௿ۄ੉঱౟ - ઱ޙ য়ܨ: " + error.getMessage()); } channel.shutdown(); }
  6. 4FSWJDF4UVQVTJOH4XJGUBOEH31$ import GRPC import SwiftProtobuf let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)

    defer { try? group.syncShutdownGracefully() } let channel = ClientConnection.insecure(group: group).connect(host: "localhost", port: 50051) let client = OrderServiceNIOClient(channel: channel) let request = PlaceOrderRequest.with { $0.customerID = "swift-user-5678" $0.orderLines = [ OrderLine.with { $0.productCode = "CAPPUCCINO"; $0.quantity = 2 }, OrderLine.with { $0.productCode = "AMERICANO"; $0.quantity = 1 } ] } do { let response = try client.placeOrder(request).response.wait() print("✅ Swift ௿ۄ੉঱౟ - ઱ޙ ৮ܐ, ઱ޙ ID: \(response.orderID)") } catch { print("❌ Swift ௿ۄ੉঱౟ - ઱ޙ য়ܨ: \(error)") }
  7. 4FSWJDF%FDMBSBUJPOVTJOH,PUMJO import kotlinx.rpc.RemoteService import kotlinx.rpc.annotations.Rpc import kotlinx.serialization.Serializable /** * Use

    cases for placing orders. */ @Rpc interface OrderPlacement : RemoteService { /** * Places an order with the provided order lines. */ suspend fun placeOrder(orderLines: List<OrderLine>): OrderId @Serializable data class OrderLine(val beverageName: String, val quantity: Int) }
  8. 4FSWJDF*NQMFNFOUBUJPOXJUILPUMJOYSQDBOE,UPS import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.routing.* import kotlinx.rpc.krpc.ktor.server.Krpc import

    kotlinx.rpc.krpc.ktor.server.rpc import kotlinx.serialization.json.Json import kotlinx.rpc.krpc.serialization.json.json fun main() { embeddedServer(Netty, port = SERVER_PORT, host = SERVER_HOST) { install(Krpc) routing { rpc("/order") { rpcConfig { serialization { json(Json) } } registerService<WeatherService> { ctx -> NoOpOrderPlacementProcessor(ctx) } } } }.start(wait = true) } class NoOpOrderPlacementProcessor( override val coroutineContext: CoroutineContext ) : OrderPlacement { override suspend fun placeOrder(orderLines: List<OrderPlacement.OrderLine>): OrderId { return OrderId(Uuid.random().toString()) } }
  9. 4FSWJDF4UVQVTJOHLPUMJOYSQDBOE,UPS import kotlinx.rpc.krpc.ktor.client.Krpc import kotlinx.rpc.krpc.ktor.client.rpc import kotlinx.rpc.krpc.ktor.client.rpcConfig import kotlinx.rpc.krpc.serialization.json.json import

    kotlinx.rpc.withService import io.ktor.client.* import io.ktor.client.plugins.websocket.WebSockets val httpClient = HttpClient { install(WebSockets) install(Krpc) } val stup = httpClient.rpc { url { host = "localhost" port = 8080 encodedPath = "/order" } rpcConfig { serialization { json() } } }.withService<OrderPlacement>() val orderLines = listOf(OrderPlacement.OrderLine(beverageName = "ESPRESSO", quantity = 1)) val orderId = stup.placeOrder(orderLines)
  10. $PNQBSJTPOXJUI&YJTUJOH31$4PMVUJPOT var channel = ManagedChannelBuilder.forAddress("localhost", 50051).usePlaintext().build(); var stub = OrderServiceGrpc.newBlockingStub(channel);

    var request = Order.PlaceOrderRequest.newBuilder() .addOrderLines(Order.OrderLine.newBuilder().setProductCode("ESPRESSO").setQuantity(2).build()) .build(); var response = stub.placeOrder(request); val httpClient = HttpClient { websockets, krpc } val stup = httpClient.rpc { url { "localhost", 8080, "/order" } rpcConfig { serialization { json() } } }.withService<OrderPlacement>() val orderLines = listOf(OrderPlacement.OrderLine(beverageName = "ESPRESSO", quantity = 1)) val orderId = stup.placeOrder(orderLines) LPUMJOYSQD H31$
  11. @Composable fun ClientApplication() { val rpcOrderClientProperties = KrpcOrderClientFactory.OrderPlacementConfig( host =

    Environment.getRequiredProperty("krpc.host") ) // Produce OrderPlacement state asynchronously; initial value is null. val orderPlacement by produceState<OrderPlacement?>(initialValue = null) { value = KrpcOrderClientFactory.buildOrderPlacement(httpClient, rpcOrderClientProperties) } // If dependencies is ready, display the ClientApplication within a themed UI. if (orderPlacement != null) { val blockingOverlayState = remember { BlockingOverlayState() } val snackbarHostState = remember { SnackbarHostState() } var appBarTitle by remember { mutableStateOf(value = "Coffeehouse") } ApplicationEventBus.registerListener<ApplicationEvent.PageLaunched> { event -> appBarTitle = event.pageName } MaterialTheme { Scaffold( topBar = TopAppBar(), snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, content = { innerPadding -> // BlockingOverlayHost wraps the OrderPage to show overlay when needed. BlockingOverlayHost(blockingOverlayState = blockingOverlayState) { OrderPage(orderPlacement!!, blockingOverlayState, snackbarHostState, innerPadding) } } ) } } else { ... } } ,PUMJO.VMUJQMBUGPSN8SJUFPODF SVOFWFSZXIFSF "OESPJE J04 XFC EFTLUPQ JVM(desktop) Android iOS
  12. %FGJOJOHUIF4FSWJDF*OUFSGBDFVTJOH,PUMJO import kotlinx.rpc.annotations.Rpc import kotlinx.rpc.RemoteService import kotlinx.coroutines.flow.Flow /** * RPC

    interface for retrieving weather information. */ @Rpc interface WeatherService : RemoteService { /** * Retrieves the current weather information for a given location. */ suspend fun obtainWeather(location: String): String /** * Continuously observes weather updates for a given location. */ suspend fun observeWeather(location: String): Flow<String> }
  13. *NQMFNFOUJOHUIF31$4FSWJDFPO4FSWFS import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow class RandomWeatherService(override val

    coroutineContext: CoroutineContext) : WeatherService { private val fetchWeather: (String) -> String = { WeatherCondition.entries.toTypedArray().random().toString() } override suspend fun obtainWeather(location: String): String { return fetchWeather(location) } override suspend fun observeWeather(location: String): Flow<String> { return flow { emit(fetchWeather(location)) // continue emitting updated weather information. } } } enum class WeatherCondition { SUNNY, CLOUDY, RAINY, SNOWY, THUNDERSTORM, FOGGY, WINDY, PARTLY_CLOUDY }
  14. $POGJHVSBUJPOUIF31$4FSWJDFPO4FSWFS import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.routing.* import kotlinx.rpc.krpc.ktor.server.Krpc import

    kotlinx.rpc.krpc.ktor.server.rpc import kotlinx.serialization.json.Json import kotlinx.rpc.krpc.serialization.json.json fun main() { embeddedServer(Netty, port = SERVER_PORT, host = SERVER_HOST) { install(Krpc) routing { rpc("/weather") { rpcConfig { serialization { json(Json) } } registerService<WeatherService> { ctx -> RandomWeatherService(ctx) } } } }.start(wait = true) }
  15. #VJMEJOHUIF31$$MJFOUXJUI4FSWJDF import kotlinx.rpc.krpc.ktor.client.Krpc import kotlinx.rpc.krpc.ktor.client.rpc import kotlinx.rpc.krpc.ktor.client.rpcConfig import kotlinx.rpc.krpc.serialization.json.json import

    kotlinx.rpc.withService import io.ktor.client.* import io.ktor.client.plugins.websocket.WebSockets val httpClient = HttpClient() { install(WebSockets) install(Krpc) } val weatherService = httpClient.rpc { url { host = "0.0.0.0" port = 8080 encodedPath = "/weather" } rpcConfig { waitForServices = true serialization { json() } } }.withService<WeatherService>()
  16. %FNP // Call obtainWeather to retrieve current weather information for

    "London". val currentWeather = weatherService.obtainWeather("London") println("Current weather in London: $currentWeather") Current weather in London: CLOUDY // Call observeWeather to continuously receive weather updates for "London". println("Observing weather updates for London:") streamScoped { // For demonstration, collect only the first 3 updates. weatherService.observeWeather("London") .take(3) .collect { update -> println("Weather update: $update") } } Observing weather updates for London: Weather update: RAINY Weather update: SUNNY Weather update: SUNNY
  17. 3FGBDUPSJOHUIF4FSWJDF*OUFSGBDFVTJOH%BUBPCKFDU import coffeehouse.modules.weather.domain.Weather import kotlinx.rpc.RemoteService import kotlinx.rpc.annotations.Rpc import kotlinx.coroutines.flow.Flow /**

    * RPC interface for retrieving weather information. */ @Rpc interface WeatherService : RemoteService { /** * Retrieves the current weather information for a given location. */ suspend fun obtainWeather(location: String): Weather /** * Continuously observes weather updates for a given location. */ suspend fun observeWeather(location: String): Flow<Weather> }
  18. %BUB4FSJBMJ[BUJPOXJUILPUMJOYTFSJBMJ[BUJPO import kotlinx.serialization.Serializable @Serializable data class Weather( val location: String,

    val temperature: Temperature, val condition: WeatherCondition, val humidity: Humidity?, val wind: Wind? ) @Serializable data class Temperature(val value: Double, val unit: String = "℃") @Serializable enum class WeatherCondition { SUNNY, CLOUDY, RAINY, SNOWY, THUNDERSTORM, FOGGY, WINDY, PARTLY_CLOUDY } @Serializable data class Wind(val speed: Double, val unit: String = "m/s") @Serializable data class Humidity(val value: Int, val unit: String = "%")
  19. LPUMJOYTFSJBMJ[BUJPOBMJCSBSZJOUIFLPUMJOFDPTZTUFNEFTJHOFEGPSTFSJBMJ[JOHEFTFSJBMJ[JOHPCKFDUT import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @Serializable data class Movie( val

    title: String, val director: String, val rating: Double = 1.0 ) val data = Movie("foo", "x", 0.1) val serialized = Json.encodeToString(Movie.serializer(), data) serialized shouldBe """{"title":"foo","director":"x","rating":0.1}""" val deserialized = Json.decodeFromString(Movie.serializer(), serialized) deserialized shouldBe data
  20. "TZODISPOPVTDBMMTXJUI$PSPVUJOFT @Rpc interface WeatherService : RemoteService { suspend fun obtainWeather(location:

    String): Weather // ... more methods for weather data } // Define a list of UK cities val cities = listOf("London", "Manchester", "Birmingham", "Edinburgh", "Glasgow") // Launch concurrent async tasks for each city to fetch weather data val weatherDeferreds = cities.map { city -> async { city to weatherService.obtainWeather(city) } } // Await all responses concurrently val weatherResults = weatherDeferreds.awaitAll() // Print out the weather for each city weatherResults.forEach { (city, weather) -> println("Current weather in $city: $weather") }
  21. "TZODISPOPVTDBMMTXJUI5JNFPVUNBOBHFNFOU try { withTimeout(1500) { // Call obtainWeather to retrieve

    current weather information for "London". val currentWeather = weatherService.obtainWeather("London") println("Current weather in London: $currentWeather") } } catch (timeout: TimeoutCancellationException) { // handle timeout }
  22. 'FBUVSFTBOE1IJMPTPQIZPGLPUMJOYSQD Service interfaces and data models are defined in pure

    Kotlin. Seamlessly integrates with Kotlin Multiplatform projects. Powered by Kotlin coroutines, RPC calls are inherently implemented as suspend functions. It is transport agnostic. library user is shielded from the details of how RPC calls are transmitted. Lightweight and fast RPC thanks to kotlinx.serialization, binary formats, and WebSocket. Backed by JetBrains, it receives continuous maintenance and development.
  23. ,PUMJODPNQBUJCJMJUZ ‣ 2.0.10 ‣ 2.10.20 ‣ 2.0.21 ‣ 2.1.0 We

    support all stable Kotlin versions starting from 2.0.0.
  24. -JNJUBUJPOTBOEGVUVSFDIBMMFOHFT The primary constraint is that both the client and

    server must use Kotlin. Shared RPC interfaces tightly couple client and server, requiring synchronized updates. Performance can be limited by the use of basic JSON serialization and WebSockets. Released in 2024, it s a relatively new and still experimental technology, currently pre 1.0. The tooling ecosystem is still developing, leaving gaps in monitoring and debugging capabilities.
  25. SFGFSFODF kotlinx.rpc github kotlinx.rpc kotlin docs kotlinx.rpc a brand new

    approach for multiplatform RPC Alexander Sysoev First steps with Kotlin RPC Kotlin Multiplatform