Kotlin library implementing the repository pattern for storing documents as JSONB in Postgres.
Provides:
- Extendible repository class, with default implementations of CRUD operations
- Optimistic locking
- Transaction management
- Migration utilities
Document Store uses the JDBI library for convenient database access.
This library is currently only distributed in Liflig internal repositories.
Contents:
Liflig Document Store works on the assumption that all database tables look alike, using the jsonb
column type from Postgres to store data. So you first have to create your table like this:
CREATE TABLE example
(
-- Can have type `text` if using `StringEntityId`, or `bigint` if using `IntegerEntityId`
id uuid NOT NULL PRIMARY KEY,
created_at timestamptz NOT NULL,
modified_at timestamptz NOT NULL,
version bigint NOT NULL,
data jsonb NOT NULL
)Then, define an entity in your application. If you use kotlinx-serialization, the entity must be
@Serializable.
import kotlinx.serialization.Serializable
import no.liflig.documentstore.entity.Entity
import no.liflig.documentstore.entity.UuidEntityId
@Serializable
data class ExampleEntity(
override val id: ExampleId,
val name: String,
) : Entity<ExampleId>
@Serializable
@JvmInline
value class ExampleId(override val value: UUID) : UuidEntityId {
override fun toString() = value.toString()
}Next, implement a Repository for your entity. See below for the implementation of
KotlinSerialization.
import no.liflig.documentstore.entity.Versioned
import no.liflig.documentstore.repository.RepositoryJdbi
import org.jdbi.v3.core.Jdbi
class ExampleRepository(jdbi: Jdbi) :
RepositoryJdbi<ExampleId, ExampleEntity>(
jdbi,
tableName = "example",
serializationAdapter = KotlinSerialization(ExampleEntity.serializer()),
) {
fun getByName(name: String): Versioned<ExampleEntity>? {
// To implement filtering on the entity's fields, we use the getByPredicate method inherited
// from RepositoryJdbi. It lets us provide our own WHERE clause, and a lambda to bind arguments.
// In the WHERE string, we use JSON operators from Postgres (->>) to query the entity's fields.
return getByPredicate("data->>'name' = :name") {
// Binds to the :name parameter in the query
bind("name", name)
}
.firstOrNull()
}
}This inherits CRUD methods (create, get, update and delete) from RepositoryJdbi, so we can
use it like this:
val exampleRepo: ExampleRepository
val (entity, version) = exampleRepo.create(
ExampleEntity(
id = ExampleId(UUID.randomUUID()),
name = "test",
)
)
println("Created entity with name '${entity.name}'")Finally, when setting up our Jdbi instance to connect to our database, we have to install the
DocumentStorePlugin (this registers argument types that RepositoryJdbi depends on):
import javax.sql.DataSource
import org.jdbi.v3.core.Jdbi
import org.jdbi.v3.core.kotlin.KotlinPlugin
import org.jdbi.v3.postgres.PostgresPlugin
fun createJdbiInstance(dataSource: DataSource): Jdbi {
return Jdbi.create(dataSource)
.installPlugin(KotlinPlugin())
.installPlugin(DocumentStorePlugin())
}The KotlinSerialization class used in the ExampleRepository above is an implementation of the
SerializationAdapter interface from Liflig Document Store. This interface allows you to choose
your own JSON serialization library when using Document Store. Here is an example implementation
for kotlinx-serialization:
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import no.liflig.documentstore.entity.Entity
import no.liflig.documentstore.repository.SerializationAdapter
private val json = Json {
encodeDefaults = true
ignoreUnknownKeys = true
}
class KotlinSerialization<EntityT : Entity<*>>(
private val serializer: KSerializer<EntityT>,
) : SerializationAdapter<EntityT> {
override fun toJson(entity: EntityT): String = json.encodeToString(serializer, entity)
override fun fromJson(value: String): EntityT = json.decodeFromString(serializer, value)
}To check build before pushing:
mvn verifyGitHub Actions will automatically release a new version for commits on master.