Beak is a functional Kotlin SQL DSL. It provides a type-safe, exception-free data access layer. Beak builds upon JetBrains' Exposed Kotlin SQL DSL library and the Arrow Kotlin functional programming library.
Beak is in early stages of development and is released as alpha software. All aspects of the API are subject to change until our first release.
Currently, Beak is built as extension functions on top of the Exposed library's DAO layer. Beak may remove Exposed as a dependency which would significantly change the API.
Currently, Beak supports H2 and MySQL. We plan to support more databases in the future.
Add Beak's Maven repository to your build configuration:
https://dl.bintray.com/codebandits/beak
Find example dependency configurations for the latest version here:
Here is a simple example comparing Exposed's .new {}
with Beak's .newOrError {}
:
import arrow.core.Either
import arrow.core.flatMap
import io.github.codebandits.beak.DataAccessError
import io.github.codebandits.beak.beakTransaction
import io.github.codebandits.beak.newOrError
import io.github.codebandits.beak.updateByIdOrError
import org.jetbrains.exposed.dao.EntityID
import org.jetbrains.exposed.dao.LongEntity
import org.jetbrains.exposed.dao.LongEntityClass
import org.jetbrains.exposed.dao.LongIdTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction
// Setup table objects and entity classes using Exposed's API:
object FeatherTable : LongIdTable("feathers") {
val type = varchar("type", 255)
}
class FeatherEntity(id: EntityID<Long>) : LongEntity(id) {
companion object : LongEntityClass<FeatherEntity>(FeatherTable)
var type by FeatherTable.type
}
fun main(args: Array<String>) {
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
transaction { create(FeatherTable) }
// Create a new Feather using Exposed's API.
// This will throw exceptions when errors are encountered.
val feather1: FeatherEntity = transaction {
FeatherEntity.new {
type = "contour"
}
}
// Create a new Feather using Beak's API.
// This will return a DataAccessError when errors are encountered.
val feather2Result: Either<DataAccessError, FeatherEntity> = FeatherEntity.newOrError {
type = "down"
}
// Combine multiple operations in one transaction to link rollbacks for all the data access operations.
// When the second operations fails the first operation will also be rolled back, returning a DataAccessError.
val feather3Result: Either<DataAccessError, FeatherEntity> = beakTransaction {
FeatherEntity.newOrError { type = "down" }
.flatMap { featherEntity ->
FeatherEntity.updateByIdOrError(featherEntity.id.value) { type = "x".repeat(500) }
}
}
when (feather3Result) {
is Either.Right -> println("operation succeeded: ${feather3Result.b}")
is Either.Left -> println("operation failed: ${feather3Result.a}") // This one prints
}
}
Beak's data access functions endeavor to never throw exceptions. Instead, they return an Either data type with a DataAccessError as the left hand side. DataAccessError is a sealed class hierarchy of all possible data access errors so you can use functional patterns to efficiently handle all data access failures in your application.
See Railway Oriented Programming for more insight into this error handling approach.