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