Code Monkey home page Code Monkey logo

rsocket-kotlin-router's Introduction

GitHub release GitHub

RSocket Router

rsocket-kotlin-router is a customisable library designed to streamline and simplify routing for RSocket Kotlin server applications. This library offers a typesafe DSL for handling various routes, serving as a declarative simplified alternative to manual routing that would otherwise result in long-winded ternary logic or exhaustive when statements.

Library provides the following features:

How to use

First of all, you need to implement basic artifacts with routing support. For now, rsocket-kotlin-router is available only at my self-hosted maven:

repositories {
    maven("https://maven.y9vad9.com")
}

dependencies {
    implementation("com.y9vad9.rsocket.router:router-core:$version")
}

For now, it's available for JVM only, but as there is no JVM platform API used, new targets will be available upon your request.

Example of defining RSocket router:

val serverRouter = router {
    routeSeparator = '.'
    routeProvider { metadata: ByteReadPacket? ->
        metadata?.read(RoutingMetadata)?.tags?.first()
            ?: throw RSocketError.Invalid("No routing metadata was provided")
    }

    routing { // this: RoutingBuilder
        route("authorization") {
            requestResponse("register") { payload: Payload ->
                // just 4 example
                println(payload.data.readText())
                Payload.Empty
            }
        }
    }
}

See also what else is supported:

Interceptors Interceptors are experimental feature: API can be changed in the future.

Preprocessors

Preprocessors are utilities that run before routing feature applies. For cases, when you need to transform input into something or propagate values using coroutines โ€“ you can extend Preprocessor.Modifier or Preprocessor.CoroutineContext. Here's an example:

class MyCoroutineContextElement(val value: String) : CoroutineContext.Element {... }

@OptIn(ExperimentalInterceptorsApi::class)
class MyCoroutineContextPreprocessor : Preprocessor.CoroutineContext {
    override fun intercept(coroutineContext: CoroutineContext, input: Payload): CoroutineContext {
        return coroutineContext + MyCoroutineContextElement(value = "smth")
    }
}

Route Interceptors

In addition to the Preprocessors, rsocket-kotlin-router also provides API to intercept specific routes:

@OptIn(ExperimentalInterceptorsApi::class)
class MyRouteInterceptor : RouteInterceptor.Modifier {
    override fun intercept(route: String, input: Payload): Payload {
        return Payload.Empty // just for example
    }
}

Installation

val serverRouter = router {
    preprocessors {
        forCoroutineContext(MyCoroutineContextPreprocessor())
    }

    sharedInterceptors {
        forModification(MyRouteInterceptor())
    }
}
Versioning support

Here's example of how request versioning looks like:

requestResponseV("foo") {
    version(1) { payload: Payload ->
        // handle requests for version "1.0"
        Payload.Empty
    }
    version(2) { payload: Payload ->
        // handle requests for version "2.0"
        Payload.Empty
    }
}

For details, please refer to the versioning guide.

Serialization support

Here is example of how type-safe requests with serialization/deserialization mechanisms look like:

requestResponse<Foo, Bar>("register") { foo: Foo ->
    return@requestResponse Bar(/* ... */)
}

// or versioned variant:

requestResponseV("register") {
    version(1) { foo: Foo ->
        Bar(/* ... */)
    }

    version(2) { qux: Qux ->
        FizzBuzz(/* ... */)
    }
}

For details, please refer to the serialization guide.

Testing

rsocket-kotlin-router provides ability to test your routes with router-test artifact:

dependencies {
    implementation("com.y9vad9.rsocket.router:router-test:$version")
}
@Test
fun testRoutes() {
    runBlocking {
        val route1 = router.routeAtOrAssert("test")
        val route2 = router.routeAtOrAssert("test.subroute")

        route1.assertHasInterceptor<MyInterceptor>()
        route2.assertHasInterceptor<MyInterceptor>()

        route2.fireAndForgetOrAssert(buildPayload {
            data("test")
        })
    }
}

You can refer to the example for more details.

Bugs and Feedback

For bugs, questions and discussions please use the GitHub Issues.

License

This library is licensed under MIT License. Feel free to use, modify, and distribute it for any purpose.

rsocket-kotlin-router's People

Contributors

y9vad9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

rsocket-kotlin-router's Issues

Add Service-based RSocket code-generation from `.proto` files

Idea

The idea is to create a user-side code generation utility within the rsocket-kotlin-router library. This utility aims to allow developers to generate RSocket-related code using protobuf. The objective is to simplify the process of defining RSocket services (especially for versioning purposes) and data models while enhancing the readability and usability of the generated code.

Implementation

Create a utility that empowers developers to define RSocket services and data models using a concise syntax reminiscent of gRPC. The utility will facilitate the generation of Kotlin code for RSocket data models, leveraging kotlinx.serialization.protobuf.

.proto definition:

service UserService {
    rpc GetUser (UserRequest) returns (UserResponse);
    rpc GetUserUpdates (UserRequest) returns (stream UserResponse);
}

Generated code:

interface UserService {
    override val identifier: String get() = "services.UsersService"

    suspend fun getUser(request: GetUserRequest): User
    fun getUserUpdates(request: GetUserRequest): Flow<User>
    // ...
}

@Serializable
data class User(
    @ProtoNumber(1) val id: Long,
    @ProtoNumber(2) val name: String
    // ...
) {
    class Builder(
        var id: Long = 0,
        // ...
    ) {
         fun build(): User = ...
    }

    companion object {
        fun create(builder: Builder.() -> Unit) { /* ... */ }
    }
}

@Serializable
data class GetUserRequest(
    @ProtoNumber(1) val user: User
) {...}

For the bi-directional streaming there'll be rsocket-like style (that is different from the gRPC):

suspend fun bidir(initPayload: XRequest, payloads: Flow<XRequests>)

For consideration: generate abstract class with two variants of bidir: previous and with single flow for better looking. [previous] will call single flow method if it's not defined.

[Preprocessors] Provide better way of intercepting requests while testing

For now, I suggest next experimental functions:

/**
 * Applies a list of preprocessors available in router to a payload and then executes a block of code with the
 * processed payload.
 *
 * @param payload The payload to be preprocessed.
 * @param block The block of code to be executed after preprocessing the payload.
 * @return The result of executing the block.
 */
@OptIn(ExperimentalRouterApi::class)
@ExperimentalInterceptorsApi
public suspend fun <R> Router.preprocess(payload: Payload, block: suspend (Payload) -> R): R

Note
It will be available only for router-test artifact.

As it has no sense to run preprocessors on route (by logic, they should run before routing feature) โ€“ I think it's the most appropriate variant for now.

Add `PostProcessor` to the `Router`

In addition to the Preprocessor, we should provide mechanism to process result of the request on interceptors level.

public interface PostProcessor : Interceptor {
    public interface Modifier : PostProcessor {
        public fun process(coroutineContext: CoroutineContext, result: Result<Payload>): Result<Payload>
    }
}

Note
process will be able to handle specific / all exceptions and convert it to the another representation of the failure if need (or log, if needed).

`router` function cannot be tested without `RSocketRequestHandlerBuilder` context

Alternative solution
We can create our own router function instead of builtin one.
Proposal
We should divide router from RSocket context in order to better testability & decomposition. Something like:

public fun router(builder: RouterBuilder.() -> Unit): Router 
public fun Router.installOn(handlerBuilder: RSocketRequestHandlerBuilder): Unit
public fun ConnectionAcceptor.installRouter(router: Router): RSocket

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.