Kora фреймворк для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный разработчиками Т-Банк / Тинькофф

Kora is a framework for writing Java / Kotlin applications with a focus on performance, efficiency, transparency made by T-Bank / Tinkoff developers

Перейти к содержанию

Vertx

Модуль предоставляет реализацию репозиториев на основе Vertx реактивного протокола работы с базой данных.

Подключение

Зависимость build.gradle:

annotationProcessor "ru.tinkoff.kora:annotation-processors"
implementation "ru.tinkoff.kora:database-vertx"

Модуль:

@KoraApp
public interface Application extends VertxDatabaseModule { }

Зависимость build.gradle.kts:

ksp("ru.tinkoff.kora:symbol-processors")
implementation("ru.tinkoff.kora:database-vertx")

Модуль:

@KoraApp
interface Application : VertxDatabaseModule

Также требуется предоставить реализацию драйвера как зависимость версии не выше 4.3.8

В отдельных случаях как например с PostgreSQL, требуется также добавить зависимость

Конфигурация

Пример полной конфигурации, описанной в классе VertxDatabaseConfig (указаны примеры значений или значения по умолчанию):

db {
    connectionUri = "postgresql://localhost:5432/postgres" //(1)!
    username = "postgres" //(2)!
    password = "postgres" //(3)!
    poolName = "kora" //(4)!
    maxPoolSize = 10 //(5)!
    connectionTimeout = "10s" //(6)!
    acquireTimeout = "0s" //(7)!
    idleTimeout = "10m" //(8)!
    cachePreparedStatements = true //(9)!
    initializationFailTimeout = "0s" //(10)!
    readinessProbe = false //(11)!
    telemetry {
        logging {
            enabled = false //(12)!
        }
        metrics {
            enabled = true //(13)!
            slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(14)!
        }
        tracing {
            enabled = true //(15)!
        }
    }
}
  1. URI подключения к базе данных (обязательный)
  2. Имя пользователя для подключения (обязательный)
  3. Пароль пользователя для подключения (обязательный)
  4. Имя набора соединений к базе данных (обязательный)
  5. Максимальный размер набора соединений к базе данных
  6. Максимальное время на установку соединения
  7. Максимальное время на получение соединения из набора соединений (по умолчанию отсутвует)
  8. Максимальное время на простой соединения
  9. Кэшировать ли подготовленные запросы
  10. Максимальное время ожидания инициализации соединения при старте сервиса (по умолчанию отсутвует)
  11. Включить ли пробу готовности для соединения базы данных
  12. Включает логгирование модуля (по умолчанию false)
  13. Включает метрики модуля (по умолчанию true)
  14. Настройка SLO для DistributionSummary метрики
  15. Включает трассировку модуля (по умолчанию true)
db:
  connectionUri = "postgresql://localhost:5432/postgres" #(1)!
  username: "postgres" #(2)!
  password: "postgres" #(3)!
  poolName: "kora" #(4)!
  maxPoolSize: 10 #(5)!
  connectionTimeout: "10s" #(6)!
  acquireTimeout: "10s" #(7)!
  idleTimeout: "10m" #(8)!
  cachePreparedStatements: true #(9)!
  initializationFailTimeout: "0s" #(10)!
  readinessProbe: false #(11)!
  telemetry:
    logging:
      enabled: false #(12)!
    metrics:
      enabled: true #(13)!
      slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(14)!
    tracing:
      enabled: true #(15)!
  1. URI подключения к базе данных (обязательный)
  2. Имя пользователя для подключения (обязательный)
  3. Пароль пользователя для подключения (обязательный)
  4. Имя набора соединений к базе данных (обязательный)
  5. Максимальный размер набора соединений к базе данных
  6. Максимальное время на установку соединения
  7. Максимальное время на получение соединения из набора соединений (по умолчанию отсутвует)
  8. Максимальное время на простой соединения
  9. Кэшировать ли подготовленные запросы
  10. Максимальное время ожидания инициализации соединения при старте сервиса (по умолчанию отсутвует)
  11. Включить ли пробу готовности для соединения базы данных
  12. Включает логгирование модуля (по умолчанию false)
  13. Включает метрики модуля (по умолчанию true)
  14. Настройка SLO для DistributionSummary метрики
  15. Включает трассировку модуля (по умолчанию true)

Можно также настроить Netty транспорт.

Использование

@Repository
public interface EntityRepository extends VertxRepository { }
@Repository
interface EntityRepository : VertxRepository

Конвертация

Возможно переопределять преобразование различных частей сущности и параметров запроса, для этого Kora предоставляет специальные интерфейсы.

Результат

Если требуется преобразовать результат в ручную, предлагается использовать VertxRowSetMapper:

final class ResultMapper implements VertxRowSetMapper<List<UUID>> {

    @Override
    public List<UUID> apply(RowSet<Row> rows) {
        // код преобразования
    }
}

@Repository
public interface EntityRepository extends VertxRepository {

    @Mapping(ResultMapper.class)
    @Query("SELECT id FROM entities")
    Mono<List<UUID>> getIds();
}
class ResultMapper : VertxRowSetMapper<List<UUID>> {
    override fun apply(rows: RowSet<Row>): List<UUID> {
        // код преобразования
    }
}

@Repository
interface EntityRepository : VertxRepository {

    @Mapping(ResultMapper::class)
    @Query("SELECT id FROM entities")
    fun getIds(): Mono<List<UUID>>
}

Строка

Если требуется преобразовать строку в ручную, предлагается использовать VertxRowMapper:

final class RowMapper implements VertxRowMapper<UUID> {

    @Override
    public UUID apply(Row row) {
        return UUID.fromString(rs.get(0, String.class));
    }
}

@Repository
public interface EntityRepository extends VertxRepository {

    @Mapping(RowMapper.class)
    @Query("SELECT id FROM entities")
    Flux<UUID> findAll();
}
class RowMapper : VertxRowMapper<UUID> {

    override fun apply(row: Row): UUID {
        return UUID.fromString(rs.get(0, String.class))
    }
}

@Repository
interface EntityRepository : VertxRepository {

    @Mapping(RowMapper::class)
    @Query("SELECT id FROM entities")
    fun findAll(): Flux<UUID>
}

Колонка

Если требуется преобразовать значение колонки в ручную, предлагается использовать VertxResultColumnMapper:

public final class ColumnMapper implements VertxResultColumnMapper<UUID> {

    @Override
    public UUID apply(Row row, int index) {
        return UUID.fromString(row.get(String.class, index));
    }
}

@Table("entities")
public record Entity(@Mapping(ColumnMapper.class) @Id UUID id, String name) { }

@Repository
public interface EntityRepository extends VertxRepository {

    @Query("SELECT * FROM entities")
    Flux<Entity> findAll();
}
class ColumnMapper : VertxResultColumnMapper<UUID> {

    override fun apply(row: Row, index: Int): UUID {
        return UUID.fromString(row.get(String.class, index))
    }
}

@Table("entities")
data class Entity(
    @Id @Mapping(ColumnMapper::class) val id: UUID,
    val name: String
)

@Repository
interface EntityRepository : VertxRepository {

    @Query("SELECT * FROM entities")
    fun findAll(): Flux<Entity>
}

Параметр

Если требуется преобразовать значение параметра запроса в ручную, предлагается использовать VertxParameterColumnMapper:

public final class ParameterMapper implements VertxParameterColumnMapper<UUID> {

    @Override
    public Object apply(@Nullable UUID value) {
        return value.toString();
    }
}

@Repository
public interface EntityRepository extends VertxRepository {

    @Query("SELECT * FROM entities WHERE id = :id")
    Flux<Entity> findById(@Mapping(ParameterMapper.class) UUID id);
}
class ParameterMapper : VertxParameterColumnMapper<UUID?> {
    override fun apply(value: UUID?): Any {
        return value.toString()
    }
}

@Repository
interface EntityRepository : VertxRepository {

    @Query("SELECT * FROM entities WHERE id = :id")
    fun findById(@Mapping(ParameterMapper::class) id: UUID): Flux<Entity>
}

Транзакции

Для выполнения ручных запросов в Kora есть интерфейс ru.tinkoff.kora.database.vertx.VertxConnectionFactory, который предоставляется в методе в рамках контракта VertxRepository. Все методы репозитория вызванные в рамках лямбды транзакции будут выполнены в этой самой транзакции.

Для того чтобы выполнять запросы транзакционно, можно использовать контракт inTx:

@Component
public final class SomeService {

    private final EntityRepository repository;

    public SomeService(EntityRepository repository) {
        this.repository = repository;
    }

    public Mono<List<Entity>> saveAll(Entity one, Entity two) {
        return repository.getVertxConnectionFactory().inTx(connection -> {
            // do some work
            return repository.insert(one) //(1)!
                    .zipWith(repository.insert(two), //(2)!
                        (r1, r2) -> List.of(one, two));
        });
    }
}
  1. Будет выполнено в рамках транзакции либо откатится если вся лямбра выкинет исключение
  2. Будет выполнено в рамках транзакции либо откатится если вся лямбра выкинет исключение
@Component
class SomeService(private val repository: EntityRepository) {

    fun saveAll(
        one: Entity,
        two: Entity
    ): Mono<List<Entity>> {
        return repository.getVertxConnectionFactory.inTx {
            repository.insert(one).zipWith(repository.insert(two)) //(1)!
            { r1: UpdateCount, r2: UpdateCount -> listOf(one, two) }
        }
    }
}
  1. Будет выполнено в рамках транзакции либо откатится если вся лямбра выкинет исключение

Ручное управление

Если для запроса нужна какая-то более сложная логика, либо запросы вне репозитория, можно использовать io.r2dbc.spi.Connection:

@Component
public final class SomeService {

    private final EntityRepository repository;

    public SomeService(EntityRepository repository) {
        this.repository = repository;
    }

    public Mono<List<Entity>> saveAll(Entity one, Entity two) {
        return repository.getVertxConnectionFactory().inTx(connection -> {
            // do some work
        });
    }
}
@Component
class SomeService(private val repository: EntityRepository) {

    fun saveAll(
        one: Entity,
        two: Entity
    ): Mono<List<Entity>> {
        return repository.getVertxConnectionFactory.inTx { connection ->
            // do some work
        }
    }
}

Сигнатуры

Доступные сигнатуры для методов репозитория из коробки:

Под T подразумевается тип возвращаемого значения, либо List<T>, либо Void, либо UpdateCount.

Под T подразумевается тип возвращаемого значения, либо T?, либо List<T>, либо Unit, либо UpdateCount.