Bota de mola sem bloqueio com corotinas Kotlin

Bota de mola sem bloqueio com corotinas Kotlin

1. Visão geral

https://www..com/kotlin-coroutines [Kotlin Coroutines] geralmente pode adicionar legibilidade a códigos reativos pesados ​​de retorno de chamada.

Neste tutorial, descobriremos como aproveitar essas corotinas para criar aplicativos https://www..com/spring-boot [Spring Boot] sem bloqueio. Também vamos comparar as abordagens reativa e corotina.

2. Motivação da Corotina

Atualmente, é comum os sistemas atenderem milhares ou até milhões de solicitações. Consequentemente, o mundo do desenvolvimento está se movendo em direção à computação sem bloqueio e ao tratamento de solicitações. A utilização eficiente dos recursos do sistema, ao descarregar as operações de E/S dos encadeamentos principais, possibilita lidar com muito mais solicitações em comparação com a abordagem tradicional por encadeamento tradicional.

O processamento assíncrono não é uma tarefa trivial e pode estar sujeito a erros. Felizmente, temos ferramentas para lidar com essa complexidade, como Java https://www..com/java-completablefuture [CompletableFutures] ou bibliotecas reativas como https://www..com/rxjava-tutorial [RxJava]. De fato, https://www..com/spring-tutorial [o framework Spring] já suporta abordagens reativas com o https://www..com/spring-reactor [Reactor] e https://www..com/frameworks spring-webflux [WebFlux].

O código assíncrono pode ser difícil de ler, mas o https://www..com/kotlin [idioma Kotlin] fornece o conceito de Coroutines para permitir a gravação de código simultâneo e assíncrono em um estilo seqüencial .

As corotinas são muito flexíveis, portanto, temos mais controle sobre a execução de tarefas por meio de trabalhos e escopos. Além disso, as corotinas Kotlin funcionam perfeitamente lado a lado com as estruturas não-bloqueadoras Java existentes .

O Spring suportará o Kotlin Coroutines da versão 5.2.

3. Configuração do Projeto

Vamos começar adicionando as dependências que precisaremos.

Como a maioria das dependências usadas neste tutorial ainda não possui versões estáveis, precisamos incluir os repositórios de instantâneos e marcos:

<pluginRepositories>
    <pluginRepository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </pluginRepository>
    <pluginRepository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
    </pluginRepository>
</pluginRepositories>

Vamos usar a estrutura https://www..com/netty [Netty], uma estrutura assíncrona orientada a eventos cliente-servidor. Usaremos NettyWebServer como uma implementação incorporada de um servidor da web reativo.

Além disso, a partir da versão 3.0, a Especificação do Servlet apresenta suporte para aplicativos para processar solicitações de maneira não-bloqueadora . Portanto, poderíamos usar um contêiner de servlet como https://www..com/jetty-embedded [Jetty] ou https://www..com/tomcat [Tomcat] também.

Vamos usar essas versões, incluindo o Spring 5.2 via Spring Boot:

<properties>
    <kotlin.version>1.3.31</kotlin.version>
    <r2dbc.version>1.0.0.M1</r2dbc.version>
    <r2dbc-spi.version>1.0.0.M7</r2dbc-spi.version>
    <h2-r2dbc.version>1.0.0.BUILD-SNAPSHOT</h2-r2dbc.version>
    <kotlinx-coroutines.version>1.2.1</kotlinx-coroutines.version>
    <spring-boot.version>2.2.0.M2</spring-boot.version>
</properties>

Em seguida, como contamos com o WebFlux para processamento assíncrono, é muito importante usar spring-boot-starter-webflux Em vez de spring-boot-starter-web . Portanto, precisamos incluir essa dependência em nosso pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

Em seguida, adicionaremos dependências R2DBC para dar suporte ao acesso reativo ao banco de dados :

<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
    <version>${h2-r2dbc.version}</version>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-spi</artifactId>
    <version>${r2dbc-spi.version}</version>
</dependency>

Por fim, adicionaremos as dependências do núcleo e das corotinas do Kotlin:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-core</artifactId>
    <version>${kotlinx-coroutines.version}</version>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-reactor</artifactId>
    <version>${kotlinx-coroutines.version}</version>
</dependency>

4. Spring Data R2DBC com Coroutines

Nesta seção, focaremos no acesso a bancos de dados nos estilos reativo e corotino.

4.1. R2DBC reativo

Vamos começar com o cliente de banco de dados relacional reativo. Simplificando, R2DBC é uma especificação de API que declara uma API reativa a ser implementada pelos fornecedores de banco de dados .

Nosso armazenamento de dados será alimentado pelo https://www..com/spring-boot-h2-database [banco de dados H2] na memória. Além disso, drivers relacionais reativos estão disponíveis para PostgreSQL e Microsoft SQL .

Primeiramente, vamos implementar um repositório simples usando a abordagem reativa:

@Repository
class ProductRepository(private val client: DatabaseClient) {

    fun getProductById(id: Int): Mono<Product> {
        return client.execute()
          .sql("SELECT *FROM products WHERE id = $1")
          .bind(0, id)
          .`as`(Product::class.java)
          .fetch()
          .one()
    }

    fun addNewProduct(name: String, price: Float): Mono<Void> {
        return client.execute()
          .sql("INSERT INTO products (name, price) VALUES($1, $2)")
          .bind(0, name)
          .bind(1, price)
          .then()
    }

    fun getAllProducts(): Flux<Product> {
        return client.select()
          .from("products")
          .`as`(Product::class.java)
          .fetch()
          .all()
    }
}

Aqui, estamos usando o DatabaseClient* sem bloqueio para executar consultas no banco de dados. Agora, vamos reescrever nossa classe de repositório usando funções de suspensão e os tipos Kotlin correspondentes.

4.2. R2DBC com corotinas

Para transformar funções de reativas para a API Coroutines , adicionamos o modificador de suspensão antes da definição da função:

fun noResultFunc(): Mono<Void>
suspend fun noResultFunc()

Além disso, podemos omitir o tipo de retorno Void. No caso de um resultado não nulo, apenas retornamos um resultado do tipo definido sem agrupá-lo na classe Mono:

fun singleItemResultFunc(): Mono<T>
fun singleItemResultFunc(): T?

Em seguida, se uma fonte puder emitir mais de um único item, basta alterar Flux para Flow da seguinte maneira:

fun multiItemsResultFunc(): Flux<T>
fun mutliItemsResultFunc(): Flow<T>

Vamos aplicar essas regras e refatorar nosso repositório:

@Repository
class ProductRepositoryCoroutines(private val client: DatabaseClient) {

    suspend fun getProductById(id: Int): Product? =
        client.execute()
          .sql("SELECT *FROM products WHERE id = $1")
          .bind(0, id)
          .`as`(Product::class.java)
          .fetch()
          .one()
          .awaitFirstOrNull()

    suspend fun addNewProduct(name: String, price: Float) =
        client.execute()
          .sql("INSERT INTO products (name, price) VALUES($1, $2)")
          .bind(0, name)
          .bind(1, price)
          .then()
          .awaitFirstOrNull()

    @FlowPreview
    fun getAllProducts(): Flow<Product> =
        client.select()
          .from("products")
          .`as`(Product::class.java)
          .fetch()
          .all()
          .asFlow()
}

No trecho acima, há vários pontos que requerem nossa atenção. De onde vêm essas funções await* _? Eles são definidos como funções de extensão Kotlin na biblioteca _kotlin-coroutines-reactive