Skip to content

konrad-kaminski/spring-kotlin-coroutine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build status Download Awesome Kotlin Badge

spring-kotlin-coroutine is a repository that contains several libraries and a demo app which allows using Kotlin coroutines in Spring applications as first-class citizens.

Project modules

This project contains several modules:

  1. spring-kotlin-coroutine - a library which allow using Kotlin coroutines in Spring applications. It contains support for @Coroutine annotation, application events, caching, scheduled tasks, CoroutineRestOperations, DeferredRestOperations, ListenableFuture extensions.
  2. spring-webmvc-kotlin-coroutine - a library which allow using Kotlin coroutines in Spring MVC applications. It contains support for web methods.
  3. spring-webflux-kotlin-coroutine - a library which allow using Kotlin coroutines in Spring Web Flux applications. It contains support for Web Flux web methods, CoroutineWebClient and functional style routes definition.
  4. spring-data-mongodb-kotlin-coroutine - a library which allow using Kotlin coroutines in Spring Data Mongo applications. It contains support for CoroutineMongoRepository and CoroutineMongoTemplate.
  5. spring-boot-autoconfigure-kotlin-coroutine - a library which contains autoconfiguration support for spring-data-mongodb-kotlin-coroutine via @EnableCoroutineMongoRepositories annotation.
  6. spring-kotlin-coroutine-demo - contains a sample application which demonstrates the use of spring-kotlin-coroutine and spring-webmvc-kotlin-coroutine.

Supported features

Most of the supported features require adding an @EnableCoroutine annotation to your Spring @Configuration class.

@EnableCoroutine

This annotation enables using the features described below. If it is possible to use a feature without this annotation it is explicitly stated so in the feature description. The annotation can be used as follows:

@Configuration
@EnableCoroutine(
    proxyTargetClass = false, mode = AdviceMode.PROXY, 
    order = Ordered.LOWEST_PRECEDENCE, schedulerDispatcher = "")
open class MyAppConfiguration {
 ...
}

The proxyTargetClass, mode and order attributes of @EnableCoroutine follow the same semantics as @EnableCaching. schedulerDispatcher is a CoroutineDispatcher used to run @Scheduled corroutines.

Note that currently only AdviceMode.PROXY mode is supported.

Web methods

Because coroutines can be suspended and they have an additional implicit callback parameter they cannot be used as web methods by default. With special parameter and result handling introduced in spring-webmvc-kotlin-coroutine you can safely use coroutines as web methods. You can e.g. have the following component:

@RestController
open class MyController {
    @GetMapping("/customers")
    suspend open fun getCustomers(): List<Customer> {
       ...
    }
}

@Coroutine

Spring beans and its methods can be annotated with @Coroutine. Using this annotation you can specify the coroutine context via the context attribute and the coroutine name via the name attribute. The context specifies a name of a bean from which a CoroutineContext can be created. Currently the following contexts/bean types are supported:

  1. CoroutineContext type beans - used directly
  2. DEFAULT_DISPATCHER - a constant specifying the Dispatchers.Default context
  3. UNCONFINED - a constant specifying the Dispatchers.Unconfined context
  4. Executor type beans - converted to CoroutineContext with asCoroutineDispatcher
  5. Rx2 Scheduler type beans - converted to CoroutineContext with asCoroutineDispatcher
  6. Reactor Scheduler type beans - converted to CoroutineContext with asCoroutineDispatcher
  7. TaskScheduler type beans - converted to CoroutineContext with asCoroutineDispatcher method of TaskSchedulerCoroutineContextResolver

You can also support your own types of beans or context names by providing Spring beans of type CoroutineContextResolver:

interface CoroutineContextResolver {
    fun resolveContext(beanName: String, bean: Any?): CoroutineContext?
}

Using @Coroutine it is quite easy to achieve the same effect as with @Async, although the code will look much simpler:

@RestController
open class MyController(
    private val repository: MyRepository
) {
    @GetMapping("/customers")
    suspend open fun getCustomers(): List<Customer> {
       return repository.getAllCustomers()
    }
}

@Component
@Coroutine(DEFAULT_DISPATCHER)
open class MyRepository {
    suspend open fun getAllCustomers(): List<Customer> {
      // a blocking code
      // which will be run with DEFAULT_DISPATCHER context
      // or simply ForkJoinPool
      ...
    }
}

Application events

Spring allows you to decouple senders and receivers of application events with the usage of ApplicationEventPublisher and @EventListener methods. They cannot, however, be coroutines. spring-kotlin-coroutine allows you to send events in a way that allows the suspension of event processing. You can also have an @EventListener method which is a coroutine.

For sending a component with the following interface can be used:

interface CoroutineApplicationEventPublisher {
    suspend fun publishEvent(event: ApplicationEvent)

    suspend fun publishEvent(event: Any)
}

Your bean can inject this interface or it can implement a CoroutineApplicationEventPublisherAware interface and have it delivered via a setCoroutineApplicationEventPublisher method:

interface CoroutineApplicationEventPublisherAware : Aware {
    fun setCoroutineApplicationEventPublisher(publisher: CoroutineApplicationEventPublisher)
}

The events sent by either CoroutineApplicationEventPublisher or ApplicationEventPublisher can be received by any method annotated with @EventListener (a coroutine or a regular one). The result of coroutine listeners will be handled in the same way as for regular listeners:

  • if it is a Unit nothing will happen
  • if it returns a single value it will be treated as a newly published event
  • if it returns an array or a collection of values it will be treated as a collection of newly published events

@Cacheable support

Due to an additional callback parameter and a special return value semantics coroutine return values cannot be cached using default Spring caching feature. However with spring-kotlin-coroutine it is possible to use a @Cacheable annotation on a coroutine, e.g.:

@Configuration
@EnableCaching
@EnableCoroutine
open class MyConfiguration {
}

@Component
class MyComponent {
    @Cacheable
    open suspend fun getCustomer(id: String): Customer {
        ...
    }
}

@Scheduled support

Coroutines annotated with @Scheduled will not work with regular Spring. However, with spring-kotlin-coroutine you can use them the same way you would do it with regular methods with the following caveats:

  1. They will be executed using CoroutineDispatcher obtained from:
  2. The exception thrown from the coroutine will be handled using:

CoroutineRestOperations

Spring provides blocking RestOperations to be used as a REST client. spring-kotlin-coroutine provides CoroutineRestOperations interface which has the same methods as RestOperations, but as coroutines:

interface CoroutineRestOperations {
    suspend fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, vararg uriVariables: Any?): T

    suspend fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, uriVariables: Map<String, *>): T

    suspend fun <T : Any?> postForObject(url: URI, request: Any?, responseType: Class<T>?): T

    ...
}

In order to create CoroutineRestOperations use the following:

val restOps = CoroutineRestOperations(restOperations = RestTemplate(), context = null)
val defaultRestOps = CoroutineRestOperations() 

If you do not specify any arguments when creating CoroutineRestOperations it will delegate all calls to RestTemplate instance and use a special coroutine context which will invoke the blocking method of RestTemplate on a separate thread (just like AsyncRestTemplate). You can specify your own RestOperations and CoroutineContext to change that behaviour.

Note that CoroutineRestOperations does not need @EnableCoroutine in order to work. Underneath CoroutineRestOperations uses createCoroutineProxy.

DeferredRestOperations

The kotlinx.coroutines library provides Deferred<T> type for non-blocking cancellable future. Based on that spring-kotlin-coroutine provides DeferredRestOperations interface which has the same methods as RestOperations, but with Deferred<T> return type:

interface DeferredRestOperations {
    fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, vararg uriVariables: Any?): Deferred<T>

    fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, uriVariables: Map<String, *>): Deferred<T>

    fun <T : Any?> postForObject(url: URI, request: Any?, responseType: Class<T>?): Deferred<T>

    ...
}

In order to create DeferredRestOperations use the following:

val restOps = DeferredRestOperations(restOperations = RestTemplate(), start = CoroutineStart.DEFAULT, context = COMMON_POOL)
val defaultRestOps = DeferredRestOperations() 

If you do not specify any arguments when creating DeferredRestOperations it will delegate all calls to RestTemplate instance and use a special coroutine context which will immediately invoke the blocking method of RestTemplate on a separate thread (just like AsyncRestTemplate). You can specify your own RestOperations and CoroutineContext to change that behaviour. By changing the start parameter value you can specify when the REST operation should be invoked (see CoroutineStart for details).

Note that DeferredRestOperations does not need @EnableCoroutine in order to work. Underneath DeferredRestOperations uses createCoroutineProxy.

Web Flux web methods

By using spring-webflux-kotlin-coroutine module instead of spring-webmvc-kotlin-coroutine web methods which are suspending functions will use Spring Web Flux. This enables them to use non-blocking I/O API.

CoroutineWebClient

CoroutineWebClient is a counterpart of the Spring Web Flux WebClient component. The differences between these components can be found mainly in the functions which operate on reactive types - in CoroutineWebClient they are suspending functions operating on regular types. Also the naming of the methods can be slightly different (e.g. in WebClient you can find bodyToMono and in CoroutineWebClient it is simply body).

Functional style routes definition

TBD

CoroutineMongoRepository

spring-data-mongodb-kotlin-coroutine contains support for CoroutineMongoRepository-based repositories. These repositories work as regular Spring Data Mongo or Spring Data Mongo Reactive repositories, but have support for suspending functions and ReceiveChannel type.

CoroutineMongoTemplate

CoroutineMongoTemplate is a counterpart of regular MongoTemplate, but contains suspending functions instead of regular ones.

EnableCoroutineMongoRepositories

The @EnableCoroutineMongoRepositories annotation works just like @EnableMongoRepositories annotation, but enables the usage of CoroutineMongoRepository repositories.

ListenableFuture extensions

The kotlinx.coroutines library provides interoperability functions with many existing asynchronous libraries ( RxJava v1, RxJava v2, CompletableFuture, etc.). However, there is no support for Spring specific ListenableFuture interface. Therefore spring-kotlin-coroutine provides the following features:

  1. fun <T> listenableFuture(context: CoroutineContext = DefaultDispatcher, block: suspend () -> T): ListenableFuture<T> - it allows you to create a ListenableFuture from a coroutine with specific coroutine context.
  2. fun <T> Deferred<T>.asListenableFuture(): ListenableFuture<T> - it allows you to create a ListenableFuture from Kotlin coroutine specific Deferred type.
  3. suspend fun <T> ListenableFuture<T>.await(): T - it allows you to create a coroutine from a ListenableFuture.

Note that these extensions do not need @EnableCoroutine in order to work.

Utility functions

Note that utility functions do not need @EnableCoroutine in order to work.

createCoroutineProxy

createCoroutineProxy can be used to create a smart proxy - an instance of an interface which will delegate all function invocations to a regular object with matching method signatures. The runtime characteristics of this proxy call depends on the types of the interface methods, the types of the proxied object methods and the proxy config. The createCoroutineProxy is declared as follows:

fun <T: Any> createCoroutineProxy(coroutineInterface: Class<T>, obj: Any, proxyConfig: CoroutineProxyConfig): T

Currently supported proxy types are as follows:

Coroutine interface method Object method Proxy config
suspend fun m(a: A): T fun m(a: A): T DefaultCoroutineProxyConfig
fun <T> m(a: A): Deferred<T> fun m(a: A): T DeferredCoroutineProxyConfig

Method.isSuspend

Method.isSuspend allows you to check if a method is a coroutine. It is defined as follows:

val Method.isSuspend: Boolean

Using in your projects

Note that this library is experimental and is subject to change.

The library is published to konrad-kaminski/maven Bintray repository.

Gradle

Add Bintray repository:

repositories {
  maven { url 'https://dl.bintray.com/konrad-kaminski/maven' }
}

Add dependencies:

compile 'org.springframework.kotlin:spring-kotlin-coroutine:0.3.7'

Note that some of the dependencies of spring-kotlin-coroutine are declared as optional. You should declare them as runtime dependencies of your application if you want to use the features that require them. The table below contains the details:

Feature Dependency
Web methods org.springframework:spring-webmvc:5.0.9.RELEASE
Rx2 Scheduler in @Coroutine org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.2.1
Reactor Scheduler in @Coroutine org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.2.1

And make sure that you use the right Kotlin version:

buildscript {
    ext.kotlin_version = '1.3.30'
}

FAQ

Why all the methods/classes have "Coroutine" as part of its name and not "Suspending"?

It's a deliberate choice. In most cases Coroutine just sounded better to me and even though sometimes Suspending might've been a better choice for consistency Coroutine was used.

License

spring-kotlin-coroutine is released under version 2.0 of the Apache License.