Skip to content

Commit

Permalink
Docs: Add structure to existing content (#86)
Browse files Browse the repository at this point in the history
* docs: split getting started document into separate topics, reformat and fix small errors

* update topic structure
  • Loading branch information
vnikolova authored Jun 13, 2024
1 parent f838ab3 commit b9211a8
Show file tree
Hide file tree
Showing 11 changed files with 988 additions and 881 deletions.
18 changes: 16 additions & 2 deletions docs/pages/kotlinx-rpc/rpc.tree
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@

<instance-profile id="rpc"
name="kotlinx.rpc"
start-page="getting_started.md">
start-page="get-started.topic">

<toc-element topic="getting_started.md"/>
<toc-element topic="get-started.topic"/>
<toc-element topic="plugins.topic"/>
<toc-element toc-title="Core concepts">
<toc-element topic="services.topic"/>
<toc-element topic="rpc-clients.topic"/>
<toc-element topic="rpc-servers.topic"/>
</toc-element>
<toc-element toc-title="Protocols">
<toc-element toc-title="kRPC">
<toc-element topic="configuration.topic"/>
<toc-element topic="features.topic"/>
<toc-element topic="transport.topic"/>
</toc-element>
</toc-element>
<toc-element topic="releases.topic"/>
</instance-profile>
99 changes: 99 additions & 0 deletions docs/pages/kotlinx-rpc/topics/configuration.topic
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
-->

<!DOCTYPE topic
SYSTEM "https://resources.jetbrains.com/writerside/1.0/xhtml-entities.dtd">
<topic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/topic.v2.xsd"
title="Configuration" id="configuration">
<p><code>RPCConfig</code> is a class used to configure <code>KRPCClient</code> and <code>KRPCServer</code>
(not to be confused with <code>RPCClient</code> and <code>RPCServer</code>).
It has two children: <code>RPCConfig.Client</code> and <code>RPCConfig.Server</code>.
<code>Client</code> and <code>Server</code> may have shared properties as well as distinct ones.
To create instances of these configurations, DSL builders are provided
(<code>RPCConfigBuilder.Client</code> class with <code>rpcClientConfig</code> function
and <code>RPCConfigBuilder.Server</code> class with <code>rpcServerConfig</code> function respectively):
</p>

<code-block lang="kotlin">
val config: RPCConfig.Client = rpcClientConfig { // same for RPCConfig.Server with rpcServerConfig
waitForServices = true // default parameter
}
</code-block>
<p>The following configuration options are available:</p>
<chapter id="serialization-dsl">
<title>
<code>serialization</code> DSL
</title>
<p>This parameter defines how serialization should work in RPC services
and is present in both client and server configurations.</p>
<p>The serialization process is used to encode and decode data in RPC requests,
so that the data can be transferred through the network.</p>
<p>Currently only <code>StringFormat</code> and <code>BinaryFormat</code> from
<a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a> are supported,
and by default you can choose from Json, Protobuf or Cbor formats:</p>

<code-block lang="kotlin">
rpcClientConfig {
serialization {
json { /* this: JsonBuilder from kotlinx.serialization */ }
cbor { /* this: CborBuilder from kotlinx.serialization */ }
protobuf { /* this: ProtobufBuilder from kotlinx.serialization */ }
}
}
</code-block>
<p>Only last defined format will be used to serialize requests.
If no format is specified, the error will be thrown.
You can also define a custom format.</p>
</chapter>
<chapter id="sharedflowparameters-dsl">
<title>
<code>sharedFlowParameters</code> DSL
</title>

<code-block lang="kotlin">
rpcClientConfig {
sharedFlowParameters {
replay = 1 // default parameter
extraBufferCapacity = 10 // default parameter
onBufferOverflow = BufferOverflow.SUSPEND // default parameter
}
}
</code-block>
<p>This parameter is needed to decode <code>SharedFlow</code> parameters received from a peer.
<code>MutableSharedFlow</code>, the default function to create a <code>SharedFlow</code> instance,
has the following signature:</p>

<code-block lang="kotlin">
fun &lt;T&gt; MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow&lt;T&gt; { /* ... */
}
</code-block>
<p>It creates a <code>SharedFlowImpl</code> class that contains these parameters as properties,
but this class in internal in <code>kotlinx.coroutines</code> and neither <code>SharedFlow</code>,
nor <code>MutableShatedFlow</code> interfaces define these properties,
which makes it impossible (at least for now) to send these properties from one endpoint to another.
But instances of these flows when deserialized should be created somehow,
so to overcome this - configuration parameter is created.
Configuration builder allows defining these parameters
and produces a builder function that is then placed into the <code>RPCConfig</code>.</p>
</chapter>
<chapter id="waitforservices-dsl">
<title>
<code>waitForServices</code> DSL
</title>
<p><code>waitForServices</code> parameter is available for both client and server.
It specifies the behavior for an endpoint in situations
when the message for a service is received,
but the service is not present in <code>RPCClient</code> or <code>RPCServer</code>.
If set to <code>true</code>, the message will be stored in memory,
otherwise, the error will be sent to a peer endpoint,
saying that the message was not handled.
Default value is <code>true</code>.</p>
</chapter>
</topic>
174 changes: 174 additions & 0 deletions docs/pages/kotlinx-rpc/topics/features.topic
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
-->

<!DOCTYPE topic
SYSTEM "https://resources.jetbrains.com/writerside/1.0/xhtml-entities.dtd">
<topic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/topic.v2.xsd"
title="Features" id="features">
<chapter title="Send and receive Flows" id="send-and-receive-flows">
<p>You can send and receive <a href="https://kotlinlang.org/docs/flow.html">Kotlin Flows</a> in RPC
methods.
That includes <code>Flow</code>, <code>SharedFlow</code>, and <code>StateFlow</code>, but
<control>NOT</control>
their mutable versions.
Flows can be nested and can be included in other classes, like this:
</p>

<code-block lang="kotlin">
@Serializable
data class StreamResult {
@Contextual
val innerFlow: StateFlow&lt;Int&gt;
}

interface MyService : RPC {
suspend fun sendStream(stream: Flow&lt;Flow&lt;Int&gt;&gt;): Flow&lt;StreamResult&gt;
}
</code-block>
<p>Note that flows that are declared in classes (like in <code>StreamResult</code>) require
<a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization">Contextual</a>
annotation.</p>
<p>To use flows in your code - you need to use special <code>streamScoped</code> function
that will provide your flows with their lifetime:</p>

<code-block lang="kotlin">
interface MyService {
suspend fun sendFlow(flow: Flow&lt;Int&gt;)
}

val myService = rpcClient.withService&lt;MyService&gt;()

streamScoped {
val flow = flow {
repeat(10) { i -&gt;
emit(i)
}
}

myService.sendFlow(flow)
}
</code-block>
<p>In that case all your flows, including incoming and outgoing,
will work until the <code>streamScoped</code> function completes.
After that, all streams that are still live will be closed.</p>
<p>You can have multiple RPC call and flows inside the <code>streamScoped</code> function, even from
different services.</p>
<p>On server side, you can use <code>invokeOnStreamScopeCompletion</code> handler inside your methods
to execute code after <code>streamScoped</code> on client side has closed.
It might be useful to clean resources, for example:</p>

<code-block lang="kotlin">
override suspend fun hotFlow(): StateFlow&lt;Int&gt; {
val state = MutableStateFlow(-1)

incomingHotFlowJob = launch {
repeat(Int.MAX_VALUE) { value -&gt;
state.value = value

hotFlowMirror.first { it == value }
}
}

invokeOnStreamScopeCompletion {
incomingHotFlowJob.cancel()
}

return state
}
</code-block>
<p>Note that this API is experimental and may be removed in future releases.</p>
</chapter>
<chapter title="Service fields" id="service-fields">
<p>Our protocol provides you with an ability to declare service fields:</p>

<code-block lang="kotlin">
interface MyService : RPC {
val plainFlow: Flow&lt;Int&gt;
val sharedFlow: SharedFlow&lt;Int&gt;
val stateFlow: StateFlow&lt;Int&gt;
}

// ### Server code ###

class MyServiceImpl(override val coroutineContext: CoroutineContext) : MyService {
override val plainFlow: Flow&lt;Int&gt; = flow {
emit(1)
}

override val sharedFlow: SharedFlow&lt;Int&gt; = MutableSharedFlow(replay = 1)
override val stateFlow: StateFlow&lt;Int&gt; = MutableStateFlow(value = 1)
}
</code-block>
<p>Field declarations are only supported for these three types: <code>Flow</code>,
<code>SharedFlow</code> and <code>StateFlow</code>.</p>
<p>You don't need to use <code>streamScoped</code> function to work with streams in fields.</p>
<p>To learn more about the limitations of such declarations,
see <a anchor="field-declarations-in-services">Field declarations in services</a>.</p>
</chapter>
<chapter title="Field declarations in services" id="field-declarations-in-services">
<p>Fields are supported in the in-house RPC protocol,
but the support comes with its limitations.
There always will be a considerable time gap between the
initial access to a field and the moment information about this field arrives from a server.
This makes it hard to provide good uniform API for all possible field types,
so now will limit supported types to <code>Flow</code>, <code>SharedFlow</code> and
<code>StateFlow</code>
(excluding mutable versions of them).
To work with these fields, you may use additional provided APIs:</p>
<p>Firstly, we define two possible states of a flow field:
<emphasis>uninitialized</emphasis>
and
<emphasis>initialized</emphasis>
.
Before the first information about this flow has arrived from a server,
the flow is in
<emphasis>uninitialized</emphasis>
state.
In this state, if you access any of its
<control>fields</control>
(<code>replayCache</code> for <code>SharedFlow</code> and <code>StateFlow</code>, and <code>value</code>
for <code>StateFlow</code>)
you will get a <code>UninitializedRPCFieldException</code>.
If you call a suspend <code>collect</code> method on them,
execution will suspend until the state is
<emphasis>initialized</emphasis>
and then the actual <code>collect</code> method will be executed.
The same ability to suspend execution until the state is
<emphasis>initialized</emphasis>
can be achieved by using <code>awaitFieldInitialization</code> function:
</p>

<code-block lang="kotlin">
interface MyService : RPC {
val flow: StateFlow&lt;Int&gt;
}

// ### Somewhere in client code ###
val myService: MyService = rpcClient.withService&lt;MyService&gt;()

val value = myService.flow.value // throws UninitializedRPCFieldException
val value = myService.awaitFieldInitialization { flow }.value // OK
// or
val value = myService.awaitFieldInitialization().flow.value // OK
// or
val firstFive = myService.flow.take(5).toList() // OK
</code-block>
<p>Secondly, we provide you with an instrument to make initialization faster.
By default, all fields are lazy.
By adding <code>@RPCEagerField</code> annotation, you can change this behavior,
so that fields will be initialized when the service in created
(when <code>withService</code> method is called):</p>

<code-block lang="kotlin">
interface MyService : RPC {
val lazyFlow: Flow&lt;Int&gt; // initialized on first access

@RPCEagerField
val eagerFlow: Flow&lt;Int&gt; // initialized on service creation
}
</code-block>
</chapter>
</topic>
Loading

0 comments on commit b9211a8

Please sign in to comment.