
DDD-oriented persistence abstraction offering Repository and Unit of Work patterns, type-safe property-based filter DSL, transactional unit-of-work hooks, and interchangeable storage adapters.
DDD for Kotlin.
ddd.kt provides a unified, and simple API for building our domains.
dependencies {
implementation("io.github.briangits.ddd:core:<version>")
}import kotlin.uuid.Uuid
import io.github.briangits.ddd.repository.Entity
data class User(
override val id: Uuid = Uuid.random(),
val name: String,
val email: String?
) : Entity<Uuid>Define properties to be used in queries when interacting with User:
import io.github.briangits.ddd.repository.EntityProperties
object UserProperties : EntityProperties<User, Uuid>() {
val name by User::name
val email by User::email
}Then define a repository:
import io.github.briangits.ddd.repository.Repository
interface UserRepository : Repository<User, UserProperties>import io.github.briangits.ddd.Persistence
import io.github.briangits.ddd.repository.get
suspend fun registerUser(persistence: Persistence) = persistence.transaction {
val users = get<UserRepository>()
users.save(User(name = "Jane Doe", email = "jane@company.com"))
users.findAll {
name startsWith "Jane"
email endsWith "@company.com"
}
}Filters use the defined properties to provide a type safe DSL filtering entities.
Multiple conditions in the top level block are combined with AND.
| Operation | Syntax |
|---|---|
| Equality |
prop eq value, prop neq value
|
| Comparison |
prop gt value, prop gte value, prop lt value, prop lte value
|
| Range |
prop.between(start, end), prop.notBetween(start, end)
|
| Text |
prop contains text, prop startsWith text, prop endsWith text
|
| Regular expression | prop matches pattern |
| Membership |
prop includedIn values, prop notIn values
|
| Nullability |
prop.isNull(), prop.isNotNull()
|
Comparing a nullable property with null using eq or neq produces a null check.
Use allOf, oneOf, and not to group conditions:
users.findAll {
oneOf {
name startsWith "Jane"
name startsWith "John"
}
not { email.isNull() }
}For nested values, define an explode object in your properties class.
For example, if User.name is a PersonName with first and last properties:
import io.github.briangits.ddd.query.properties.explode
object UserProperties : EntityProperties<User, Uuid>() {
object name : explode<User, PersonName>(User::name) {
val first by PersonName::first
val last by PersonName::last
}
val email by User::email
}Using the exploded object:
users.find {
name.first eq "Jane"
}Or using the explode helper:
users.find {
explode(name) {
first eq "Jane"
last eq "Doe"
}
}Request a page with a size and offset. The result contains items, total,
and the pagination metadata.
import io.github.briangits.ddd.repository.pagination.Pagination
val page = users.findAll(page = Pagination(size = 20, offset = 0)) {
email endsWith "@company.com"
}Or use the convenience extension:
val page = users.findAll(limit = 20, offset = 0) {
email endsWith "@company.com"
}Use UnitOfWork to group repository operations and register hooks around commit.
dependencies {
implementation("io.github.briangits.ddd:uow:<version>")
}Define a unit of work with the repositories an operation needs:
import io.github.briangits.ddd.repository.get
import io.github.briangits.ddd.transaction.Transaction
import io.github.briangits.ddd.uow.UnitOfWork
class UserUoW(transaction: Transaction) : UnitOfWork<UserUoW>(transaction) {
val users = get<UserRepository>()
}Create a factory for your unit of work:
import io.github.briangits.ddd.Persistence
import io.github.briangits.ddd.uow.UoWFactory
val userUoW = UoWFactory(persistence) { transaction -> UserUoW(transaction) }run executes the block and commits. execute lets you perform operations before
committing explicitly. Both roll back when their block fails.
suspend fun saveUser(user: User) {
userUoW {
users.save(user)
beforeCommit { check(users.exists { id eq user.id }) }
afterCommit { println("Saved user ${user.id}") }
}
}The core API and unit-of-work module support Kotlin Multiplatform. Adapters provide the database-specific repository and transaction implementations.
DDD for Kotlin.
ddd.kt provides a unified, and simple API for building our domains.
dependencies {
implementation("io.github.briangits.ddd:core:<version>")
}import kotlin.uuid.Uuid
import io.github.briangits.ddd.repository.Entity
data class User(
override val id: Uuid = Uuid.random(),
val name: String,
val email: String?
) : Entity<Uuid>Define properties to be used in queries when interacting with User:
import io.github.briangits.ddd.repository.EntityProperties
object UserProperties : EntityProperties<User, Uuid>() {
val name by User::name
val email by User::email
}Then define a repository:
import io.github.briangits.ddd.repository.Repository
interface UserRepository : Repository<User, UserProperties>import io.github.briangits.ddd.Persistence
import io.github.briangits.ddd.repository.get
suspend fun registerUser(persistence: Persistence) = persistence.transaction {
val users = get<UserRepository>()
users.save(User(name = "Jane Doe", email = "jane@company.com"))
users.findAll {
name startsWith "Jane"
email endsWith "@company.com"
}
}Filters use the defined properties to provide a type safe DSL filtering entities.
Multiple conditions in the top level block are combined with AND.
| Operation | Syntax |
|---|---|
| Equality |
prop eq value, prop neq value
|
| Comparison |
prop gt value, prop gte value, prop lt value, prop lte value
|
| Range |
prop.between(start, end), prop.notBetween(start, end)
|
| Text |
prop contains text, prop startsWith text, prop endsWith text
|
| Regular expression | prop matches pattern |
| Membership |
prop includedIn values, prop notIn values
|
| Nullability |
prop.isNull(), prop.isNotNull()
|
Comparing a nullable property with null using eq or neq produces a null check.
Use allOf, oneOf, and not to group conditions:
users.findAll {
oneOf {
name startsWith "Jane"
name startsWith "John"
}
not { email.isNull() }
}For nested values, define an explode object in your properties class.
For example, if User.name is a PersonName with first and last properties:
import io.github.briangits.ddd.query.properties.explode
object UserProperties : EntityProperties<User, Uuid>() {
object name : explode<User, PersonName>(User::name) {
val first by PersonName::first
val last by PersonName::last
}
val email by User::email
}Using the exploded object:
users.find {
name.first eq "Jane"
}Or using the explode helper:
users.find {
explode(name) {
first eq "Jane"
last eq "Doe"
}
}Request a page with a size and offset. The result contains items, total,
and the pagination metadata.
import io.github.briangits.ddd.repository.pagination.Pagination
val page = users.findAll(page = Pagination(size = 20, offset = 0)) {
email endsWith "@company.com"
}Or use the convenience extension:
val page = users.findAll(limit = 20, offset = 0) {
email endsWith "@company.com"
}Use UnitOfWork to group repository operations and register hooks around commit.
dependencies {
implementation("io.github.briangits.ddd:uow:<version>")
}Define a unit of work with the repositories an operation needs:
import io.github.briangits.ddd.repository.get
import io.github.briangits.ddd.transaction.Transaction
import io.github.briangits.ddd.uow.UnitOfWork
class UserUoW(transaction: Transaction) : UnitOfWork<UserUoW>(transaction) {
val users = get<UserRepository>()
}Create a factory for your unit of work:
import io.github.briangits.ddd.Persistence
import io.github.briangits.ddd.uow.UoWFactory
val userUoW = UoWFactory(persistence) { transaction -> UserUoW(transaction) }run executes the block and commits. execute lets you perform operations before
committing explicitly. Both roll back when their block fails.
suspend fun saveUser(user: User) {
userUoW {
users.save(user)
beforeCommit { check(users.exists { id eq user.id }) }
afterCommit { println("Saved user ${user.id}") }
}
}The core API and unit-of-work module support Kotlin Multiplatform. Adapters provide the database-specific repository and transaction implementations.