Slide 1

Slide 1 text

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. 박용권 당근마켓

Slide 2

Slide 2 text

߅ਊӂBQQMZ\ ࣗࣘ׼Ӕ݃௄ழޭפ౭प ౠ૚݈݆਺ Oߓࣘ੤ࢤ ^

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

8IBUJT31$ 3FNPUF1SPDFEVSF$BMM DBMMQMBDF0SEFS PSEFS POTUPSFTZTUFN FYFDVUFQMBDF0SEFSQSPDFEVSF XJUIPSEFSPOTUPSFTZTUFN 31$3FRVFTU\QMBDF0SEFS PSEFS^ 31$3FTQPOTF

Slide 5

Slide 5 text

-PDBM1SPDFEVSF$BMMWT3FNPUF1SPDFEVSF$BMM 0SEFS .BOBHFNFOU 0SEFS 1SPDFTTPS $BSU $MJFOU 4FSWFS MPDBM GVODUJPO DBMM 31$

Slide 6

Slide 6 text

31$4USVDUVSF 4FSWJDF %FDMBSBUJPO 4FSWJDF4UVC 31$$MJFOU 31$4FSWFS 4FSWJDF *NQMFNFOUBUJPO USBOTQPSU

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

+PVSOFZJOUP31$XJUIH31$FYBNQMFT

Slide 9

Slide 9 text

$PGGFFIPVTFTZTUFN NPCJMF BQQMJDBUJPO TUPSF BQQMJDBUJPO QMBDFPSEFS

Slide 10

Slide 10 text

H31$4USVDUVSF (FOFSBUPS QSPUPD 4FSWJDF4UVC 31$$MJFOU 31$4FSWFS 4FSWJDF *NQMFNFOUBUJPO 4FSWJDF#BTF$MBTT )551 4FSWJDF %FDMBSBUJPO QSPUPDPMCVGGFST

Slide 11

Slide 11 text

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; }

Slide 12

Slide 12 text

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() }

Slide 13

Slide 13 text

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(); }

Slide 14

Slide 14 text

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)") }

Slide 15

Slide 15 text

%FNP

Slide 16

Slide 16 text

/FYU MFUTFYQMPSFLPUMJOYSQDFYBNQMFT

Slide 17

Slide 17 text

"HBJODPGGFFIPVTFTZTUFN NPCJMF BQQMJDBUJPO TUPSF BQQMJDBUJPO QMBDFPSEFS

Slide 18

Slide 18 text

LPUMJOYSQD4USVDUVSF 4FSWJDF %FDMBSBUJPO 4FSWJDF4UVC 31$$MJFOU 31$4FSWFS 4FSWJDF *NQMFNFOUBUJPO USBOTQPSU

Slide 19

Slide 19 text

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): OrderId @Serializable data class OrderLine(val beverageName: String, val quantity: Int) }

Slide 20

Slide 20 text

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 { ctx -> NoOpOrderPlacementProcessor(ctx) } } } }.start(wait = true) } class NoOpOrderPlacementProcessor( override val coroutineContext: CoroutineContext ) : OrderPlacement { override suspend fun placeOrder(orderLines: List): OrderId { return OrderId(Uuid.random().toString()) } }

Slide 21

Slide 21 text

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() val orderLines = listOf(OrderPlacement.OrderLine(beverageName = "ESPRESSO", quantity = 1)) val orderId = stup.placeOrder(orderLines)

Slide 22

Slide 22 text

%FNP

Slide 23

Slide 23 text

MFBSOLPUMJOYMJCSBSJFTCZFYBNQMFT https://github.com/springrunner/learn-kotlinx-libraries-by-examples/tree/main/kotlinx-rpc

Slide 24

Slide 24 text

8IZEJE,PUMJOCVJMEJUTPXOSQDUPPM

Slide 25

Slide 25 text

$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() val orderLines = listOf(OrderPlacement.OrderLine(beverageName = "ESPRESSO", quantity = 1)) val orderId = stup.placeOrder(orderLines) LPUMJOYSQD H31$

Slide 26

Slide 26 text

,PUMJOJTBNVMUJQMBUGPSNQSPHSBNNJOHMBOHVBHF 4FSWFS "OESPJE J04 %FTLUPQ 8FC $MJFOU.VMUJQMBUGPSN $PNNPO.VMUJQMBUGPSN https://kotlinlang.org/docs/multiplatform-intro.html

Slide 27

Slide 27 text

@Composable fun ClientApplication() { val rpcOrderClientProperties = KrpcOrderClientFactory.OrderPlacementConfig( host = Environment.getRequiredProperty("krpc.host") ) // Produce OrderPlacement state asynchronously; initial value is null. val orderPlacement by produceState(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 { 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

Slide 28

Slide 28 text

,PUMJO.VMUJQMBUGPSN8SJUFPODF SVOFWFSZXIFSF "OESPJE J04 XFC EFTLUPQ https://www.inflearn.com/course/௏ౣܽ-ݣ౭೒ۖಬ

Slide 29

Slide 29 text

#VJMEJOHB#FUUFS%FW&YGPS.VMUJQMBUGPSN31$JO,PUMJO JVM Android Wasm Native ^ iOS JavaScript

Slide 30

Slide 30 text

(FUUJOHUPLOPXLPUMJOYSQD

Slide 31

Slide 31 text

%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 }

Slide 32

Slide 32 text

*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 { return flow { emit(fetchWeather(location)) // continue emitting updated weather information. } } } enum class WeatherCondition { SUNNY, CLOUDY, RAINY, SNOWY, THUNDERSTORM, FOGGY, WINDY, PARTLY_CLOUDY }

Slide 33

Slide 33 text

$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 { ctx -> RandomWeatherService(ctx) } } } }.start(wait = true) }

Slide 34

Slide 34 text

#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()

Slide 35

Slide 35 text

%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

Slide 36

Slide 36 text

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 }

Slide 37

Slide 37 text

%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 = "%")

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

LPUMJOYTFSJBMJ[BUJPOBMJCSBSZJOUIFLPUMJOFDPTZTUFNEFTJHOFEGPSTFSJBMJ[JOHEFTFSJBMJ[JOHPCKFDUT https://youtu.be/bnJB3VYCqhU?feature=shared LPUMJOYTFSJBMJ[BUJPO ё୓ܳ֎౟ਕ௼ܳా೧੹࣠ೞѢաؘ੉ఠ߬੉झژח౵ੌী੷੢ೡࣻ੓חഋधਵ۽߸ജೞח೐۽ࣁझ

Slide 40

Slide 40 text

"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") }

Slide 41

Slide 41 text

"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 }

Slide 42

Slide 42 text

%FFQ%JWFJOUPLPUMJOYSQD

Slide 43

Slide 43 text

)PX31$3FRVFTUT"SF1SPDFTTFE

Slide 44

Slide 44 text

5SBOTQPSUTVQQPSUEBUBFYDIBOHFCFUXFFOTZTUFNT 4FSWJDF4UVC 31$$MJFOU 31$4FSWFS 4FSWJDF *NQMFNFOUBUJPO USBOTQPSU ,UPS XFCTPDLFU L31$QSPUPDPM H31$QSPUPDPM FYQFSJNFOUBM UIFJSPXOQSPUPDPM OPUSFDPNNFOEFE

Slide 45

Slide 45 text

8SBQQJOHVQPVSKPVSOFZUPLPUMJOYSQD

Slide 46

Slide 46 text

'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.

Slide 47

Slide 47 text

,PUMJODPNQBUJCJMJUZ ‣ 2.0.10 ‣ 2.10.20 ‣ 2.0.21 ‣ 2.1.0 We support all stable Kotlin versions starting from 2.0.0.

Slide 48

Slide 48 text

7FSTJPO)JTUPSZ QSPKFDUTUBSUFE SFMFBTFE YSFMFBTFE MJWF SFMFBTFE

Slide 49

Slide 49 text

-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.

Slide 50

Slide 50 text

HJUIVCDPNBSBXO MJOLFEJODPNJOBSBXO end.rpc ߅ਊӂ

Slide 51

Slide 51 text

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