Kotlin integration
Table of contents
Coroutines support for annotated services
You can implement annotated services with Kotlin coroutines by adding the following dependency:
dependencies {
implementation platform('com.linecorp.armeria:armeria-bom:1.33.4')
...
implementation 'com.linecorp.armeria:armeria-kotlin'
}Then, define the suspending function:
class MyAnnotatedService {
@Get("/users/{name}")
suspend fun getUserToken(@Param("name") name: String): String {
val user = myReactiveRepository.findByName(name).await()
return user.token
}
}Note that you can omit the value of @Param if you compiled your Kotlin code with java-parameters option.
class MyAnnotatedService {
@Get("/users/{name}")
suspend fun getUserToken(@Param name: String): String { ... }
}Data classes are also supported for JSON requests and responses:
data class User(val name: String, val token: String)
data class Result(val status: Int, val message: String)
class MyRestService {
@ConsumesJson
@ProducesJson
@Post("/users")
suspend fun createUser(user: User): Result {
return myReactiveRepository.save(user).await()
}
}Coroutine context and dispatcher
ServiceRequestContext is injected to the default CoroutineContext of a suspend function,
so you can access ServiceRequestContext from a suspend function with current()/
and the context is inherits to child contexts.
class MyContextAwareService {
suspend fun contextPropagation(@Param name: String): String {
// Make sure that current thread is request context aware
assert(ServiceRequestContext.current() != null)
return withContext(myDispatcher) {
// Make sure that the ServiceRequestContext is propagated to other CoroutineContext which is needed
// for logging, tracing.
assert(ServiceRequestContext.current() != null)
...
}
}
}Coroutine dispatcher
By default, Armeria uses a context-aware event loop as a
coroutine dispatcher to run suspend functions.
A context-aware blocking executor is used as a dispatcher if you annotate a service method with
@Blocking or enable AnnotatedServiceBindingBuilder.useBlockingTaskExecutor().
serverBuilder
.annotatedService()
// Enable `useBlockingTaskExecutor`.
.useBlockingTaskExecutor(true)
.build(MyAnnotatedService())
// or use @Blocking annotation:
import com.linecorp.armeria.server.annotation.Blocking
class MyAnnotatedService {
@Blocking
@Get("/blocking/users/{name}")
suspend fun getUserTokenBlocking(@Param("name") name: String): String {
val id = myReactiveClient.getId(name).await()
val user = myBlockingRepository.findById(id)
return user.token
}
}Customizing CoroutineContext for annotated services
Armeria provides a way to configure a coroutine context of annotated service methods
using CoroutineContextService decorator.
import com.linecorp.armeria.server.kotlin.CoroutineContextService
serverBuilder
.annotatedService()
.decorator(CoroutineContextService.newDecorator { ctx ->
CoroutineName(ctx.config().defaultServiceNaming().serviceName(ctx) ?: "name")
})
.build(MyAnnotatedService())Coroutines support for RestClient
Armeria provides extension methods to convert RestClientPreparation.execute() to a suspend function.
val client = RestClient.of("https://armeria.dev")
// In a coroutine scope.
val response: ResponseEntity<RestResponse> =
client.get("/api/v1/users/{id}")
.pathParam("id", "Armeria")
.header(HttpHeaderNames.AUTHORIZATION, "...")
// The suspend function executes the request and
// returns the response.
.execute<RestResponse>()Coroutines support for gRPC-Kotlin
To use gRPC-Kotlin with Armeria, add the following dependency:
dependencies {
implementation platform('com.linecorp.armeria:armeria-bom:1.33.4')
...
implementation 'com.linecorp.armeria:armeria-grpc-kotlin'
}Running a gRPC-Kotlin service
gRPC-kotlin support is enabled automatically by registering coroutine stub services in the same way as using gRPC-Java.
import com.linecorp.armeria.server.Server
import com.linecorp.armeria.server.docs.DocService
import com.linecorp.armeria.server.grpc.GrpcService
class YourServiceImpl : YourServiceGrpcKt.YourServiceCoroutineImplBase() {
...
}
// Creates GrpcService with your gRPC stub generated by gRPC-Kotlin.
val grpcService =
GrpcService
.builder()
// Add your gRPC-Kotlin stub to GrpcService
.addService(YourServiceImpl())
.enableUnframedRequests(true)
.build()
// Creates Armeria Server for gRPC-Kotlin stub.
Server
.builder()
.http(httpPort)
.https(httpsPort)
.service(grpcService)
// Add DocService for browsing the list of gRPC services and
// invoking a service operation from a web form.
// See https://armeria.dev/docs/server-docservice for more information.
.serviceUnder("/docs", DocService())
.build()Calling a gRPC-Kotlin service
You can use GrpcClients to specify a coroutine stub that ends with CoroutineStub when creating a
gRPC-Kotlin client.
val client =
GrpcClients
.builder("https://grpc.service.com")
.intercept(yourInterceptors)
.build(YourServiceGrpcKt.YourServiceCoroutineStub::class.java)Customizing CoroutineContext for gRPC-Kotlin services
When you use gRPC-Kotlin's suspend functions, the default CoroutineContext is the combination of
ServiceRequestContext and an event loop CoroutineDispatcher. You can access
ServiceRequestContext from the suspend functions with ServiceRequestContext.current(),
similar to how you access it in annotated services.
With Armeria's integration with gRPC-Kotlin, it's easy to set up a custom CoroutineContext for your
gRPC-Kotlin service. You can create your own CoroutineContext using CoroutineContextProvider, and
add it to your service through Java SPI.
Note that the ServiceRequestContext is still a part of the
CoroutineContext, but the event loop CoroutineDispatcher is disabled if the custom CoroutineContext
is used.
class CustomCoroutineContextProvider : CoroutineContextProvider {
override fun provide(ctx: ServiceRequestContext): CoroutineContext {
return ... // A custom CoroutineContext
}
}