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 {}:
importarrow.core.Eitherimportarrow.core.flatMapimportio.github.codebandits.beak.DataAccessErrorimportio.github.codebandits.beak.beakTransactionimportio.github.codebandits.beak.newOrErrorimportio.github.codebandits.beak.updateByIdOrErrorimportorg.jetbrains.exposed.dao.EntityIDimportorg.jetbrains.exposed.dao.LongEntityimportorg.jetbrains.exposed.dao.LongEntityClassimportorg.jetbrains.exposed.dao.LongIdTableimportorg.jetbrains.exposed.sql.Databaseimportorg.jetbrains.exposed.sql.SchemaUtils.createimportorg.jetbrains.exposed.sql.transactions.transaction// Setup table objects and entity classes using Exposed's API:object FeatherTable : LongIdTable("feathers") {
val type = varchar("type", 255)
}
classFeatherEntity(id:EntityID<Long>) : LongEntity(id) {
companionobject:LongEntityClass<FeatherEntity>(FeatherTable)
var type by FeatherTable.type
}
funmain(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) {
isEither.Right->println("operation succeeded: ${feather3Result.b}")
isEither.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.