Please provide a way to bind objects to the request and acces them from anywhere
- Dominant language
- Kotlin
- Stars
- 14.5k
- Forks
- 1.3k
- Avg merge
- 2d 13h
- Merged PRs (30d)
- 49
Description
In Ktor, accessing the incoming call is easy: just use the `call` field. However, this field is not global, it is provided by the REST API builder.
Assume you are building a microservice with Ktor. You have your REST endpoints, a service layer, and some repositories - a typical 3-level enterprise stack. You do authentication via JWT. All of that works nicely with Ktor.
Now we would like to call another microservice from one of our services. To do so, we require the JWT token which was passed to the call. We're in the middle of a service-layer method, probably 5 or 6 function calls deep into the JVM call stack. The `call` object (which contains the token in its header) is way out of reach. Passing it down as an argument through all the functions in the service layer would be a major pain as well.
Coming from the Spring framework, for a case like this, you have the `SecurityContext`. You would call the static method `SecurityContextHolder.getContext()` and get the exact instance of the context which is associated with your current call. This is accomplished via a `ThreadLocal` that is hidden inside the `SecurityContextHolder`. When a call is received, Spring generates the security context, and clears it when call processing is done.
Other prominent use cases for request-attached data are:
- Sessions maintained by O/R-mappers (e.g. JPA)
- Database transactions
- Access control decisions
As far as I know, Ktor at the moment provides no such facilities. Mimicking the spring approach directly is also not feasible, as coroutines and `ThreadLocal` in general do not play nice with each other.
I've found [this article](https://blog.tpersson.io/2018/04/22/emulating-request-scoped-objects-with-kotlin-coroutines/) which outlines an approach for storing such data in the `CoroutineContext`. However, this has some issues as well:
- Coroutine contexts are, to my knowledge, not nested by default. So whenever I start a sub-coroutine (e.g. via `async`), the context of the parent coroutine (the one which invoked `async()`) is not visible to the child (the one which was produced by `async()`). This is in general very unfortunate, because the programmer A) needs to be aware that the context contains data which might be needed downstream and B) needs to pass the data along explicitly in the coroutine generator function.
- The coroutine context API is very convoluted. I just tried to follow the article, the developer experience is honestly quite bad (in essence it's just a hash map, with type safety factored in). It feels like I have to write a lot of code to accomplish something that is simple, and that is also an often-needed feature. The upshot is: ideally I shouldn't have to touch it as a Ktor user.
The Ktor-side API which I would imagine is something like this:
```kotlin
object CallContext {
suspend fun get(key: String): Any { ... }
suspend fun set(key: String, value: Any): Any { ... }
}
```
... and ideally the programmer should not have to explicitly pass along coroutine contexts. The functions are `suspend`ing to force the caller to use them from within a coroutine (this allows the implementation to make use of the `coroutineContext`). I'm not super experienced with coroutines (yet), so maybe there is a way to do it like this even today. Maybe Ktor already has something like this?
Please feel free to correct me if I took a wrong turn somewhere. I'm still quite new to all of this.
Contributor guide
Research direction
The issue names no file or test; begin by reviewing how Ktor exposes the incoming call to REST endpoints and how coroutineContext behaves across service-layer calls and async work. Compare that with the proposed CallContext API and determine the request-data lifecycle and usage expectations before defining what a complete solution should provide.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kotlin
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100