Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repository with suspend methods #9

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

# DDD Repository pattern for [Lagom](https://www.lagomframework.com)/[Play](https://playframework.com)

API of library contains only one interface [Repository](https://www.javadoc.io/doc/org.taymyr.play/play-repository-api-java) for DDD aggregate, inspired the book
API of library contains interface [Repository](https://www.javadoc.io/doc/org.taymyr.play/play-repository-api-java) for DDD aggregate, inspired the book
[Implementing Domain-Driven Design](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577) by Vaughn Vernon.
The library also contains the [KRepository](https://www.javadoc.io/doc/org.taymyr.play/play-repository-api-java) interface for supporting [coroutines](https://kotlinlang.org/docs/coroutines-overview.html).

## Example

Expand All @@ -21,6 +22,10 @@ Create the interface of repository for aggregate
public interface AggregateRepository extends Repository<Aggregate, UUID> { }
```

```kotlin
interface AggregateKRepository : KRepository<Aggregate, UUID>
```

and implement it

```java
Expand All @@ -38,6 +43,18 @@ public class AggregateRepositoryImpl extends JPARepository<Aggregate, UUID> impl
}
```

```kotlin
class AggregateKRepositoryImpl @Inject constructor(
jpaApi: JPAApi,
executionContext: DatabaseExecutionContext,
): KJPARepository<Aggregate, UUID>(jpaApi, executionContext, Aggregate::class.java), AggregateKRepository {

override fun nextIdentity(): UUID {
return UUID.randomUUID()
}
}
```

## Contributors

Other persistence implementations (for _MongoDB_/_Cassandra_/_Redis_) are welcome.
Expand Down
6 changes: 4 additions & 2 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {

val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions.jvmTarget = "1.8"
compileKotlin.kotlinOptions.freeCompilerArgs += listOf("-Xjvm-default=enable", "-Xjsr305=strict")
compileKotlin.kotlinOptions.freeCompilerArgs += listOf("-Xjvm-default=all", "-Xjsr305=strict")

dependencies {
implementation(kotlin("stdlib-jdk8"))
Expand All @@ -20,7 +20,9 @@ dependencies {
ktlint {
version.set(Versions.ktlint)
outputToConsole.set(true)
reporters.set(setOf(ReporterType.CHECKSTYLE))
reporters {
reporter(ReporterType.CHECKSTYLE)
}
}

val sourcesJar by tasks.creating(Jar::class) {
Expand Down
142 changes: 142 additions & 0 deletions api/src/main/kotlin/org/taymyr/play/repository/domain/KRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package org.taymyr.play.repository.domain

import akka.Done

/**
* DDD repository for identified aggregate (use coroutines).
*/
interface KRepository<Aggregate, Identity> {

/**
* Generate a new identifier.
*/
fun nextIdentity(): Identity

/**
* Get aggregate by the identifier.
* @param id Identifier.
* @return aggregate or null if aggregate not exist.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun get(id: Identity): Aggregate?

/**
* Get all aggregates from the repository.
* @return List of aggregates or an empty list if the repository is empty.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun getAll(): List<Aggregate>

/**
* Finding aggregates on the repository by their identifiers.
* @param ids List of identifiers.
* @return List of aggregates or an empty list if aggregates not found.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun findByIds(ids: List<Identity>): List<Aggregate>

/**
* Finding aggregates on the repository by their identifiers.
* @param ids List of identifiers.
* @return List of aggregates or an empty list if aggregates not found.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun findByIds(vararg ids: Identity): List<Aggregate> = findByIds(ids.asList())

/**
* Removing aggregate from the repository.
* @param aggregate Aggregate.
* @return [Done] if removing successfully. Otherwise will throw an exception.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun remove(aggregate: Aggregate): Done

/**
* Removing aggregates from the repository.
* @param aggregates List of aggregates.
* @return [Done] if removing successfully. Otherwise will throw an exception.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun removeAll(aggregates: Collection<Aggregate>): Done

/**
* Create aggregate on the repository.
* @param aggregate Aggregate.
* @return [Done] if creation successfully. Otherwise will throw an exception.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun create(aggregate: Aggregate): Done

/**
* Create aggregates on the repository.
* @param aggregates Aggregates.
* @return [Done] if creation successfully. Otherwise will throw an exception.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun createAll(aggregates: Collection<Aggregate>): Done

/**
* Saving aggregate on the repository.
* @param aggregate Aggregate.
* @return [Done] if saving successfully. Otherwise will throw an exception.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun save(aggregate: Aggregate): Done

/**
* Saving aggregates on the repository.
* @param aggregates Aggregates.
* @return [Done] if saving successfully. Otherwise will throw an exception.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun saveAll(aggregates: Collection<Aggregate>): Done

/**
* Finding aggregates on the repository by jpaQuery.
* @param jpaQuery Jpa query.
* @param parameters Map of parameters name and value in jpa query.
* @return List of aggregates or an empty list if aggregates not found.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun findAggregates(jpaQuery: String, parameters: Map<String, Any>): List<Aggregate>

/**
* Finding aggregates on the repository by jpaQuery and using pagination.
* @param jpaQuery Jpa query.
* @param parameters Map of parameters name and value in jpa query.
* @param offset Offset from the beginning of the list
* @param limit The number of elements in the sample.
* @return List of aggregates or an empty list if aggregates not found.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun findAggregates(jpaQuery: String, parameters: Map<String, Any>, offset: Int, limit: Int): List<Aggregate>

/**
* Finding aggregate on the repository by jpaQuery.
* @param jpaQuery Jpa query.
* @param parameters Map of parameters name and value in jpa query.
* @return aggregate or null if aggregate not exist.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun findAggregate(jpaQuery: String, parameters: Map<String, Any>): Aggregate?

/**
* Finding specific values on the repository by jpaQuery.
* @param jpaQuery Jpa query.
* @param parameters Map of parameters name and value in jpa query.
* @param clazz Class of the find value.
* @return List of aggregates or an empty list if aggregates not found.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun <E> findValues(jpaQuery: String, parameters: Map<String, Any>, clazz: Class<E>): List<E>

/**
* Find specific value on the repository by jpaQuery.
* @param jpaQuery Jpa query.
* @param parameters Map of parameters name and value in jpa query.
* @param clazz Class of the find value.
* @return Specific value or null.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
suspend fun <E> findValue(jpaQuery: String, parameters: Map<String, Any>, clazz: Class<E>): E?
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ interface Repository<Aggregate, Identity> {
* @return List of aggregates or an empty list if aggregates not found.
* @throws Exception Any exceptions while execute a query on the database will wrapped.
*/
@JvmDefault
fun findByIds(vararg ids: Identity): CompletionStage<List<Aggregate>> = findByIds(ids.asList())

/**
Expand Down
29 changes: 18 additions & 11 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@ allprojects {
mavenCentral()
jcenter()
}
apply<JacocoPlugin>()
jacoco {
toolVersion = Versions.jacoco
}
}

subprojects {
group = "org.taymyr.play"
version = "0.2.0-SNAPSHOT"

apply<JacocoPlugin>()
apply<NexusPublishPlugin>()

jacoco {
toolVersion = Versions.jacoco
}

nexusPublishing {
repositories {
sonatype()
Expand All @@ -53,12 +52,20 @@ val jacocoAggregateReport by tasks.creating(JacocoReport::class) {
reports {
xml.isEnabled = true
}
additionalClassDirs(files(subprojects.flatMap { project ->
listOf("scala", "kotlin").map { project.buildDir.path + "/classes/$it/main" }
}))
additionalSourceDirs(files(subprojects.flatMap { project ->
listOf("scala", "kotlin").map { project.file("src/main/$it").absolutePath }
}))
additionalClassDirs(
files(
subprojects.map { project ->
listOf("scala", "kotlin").map { project.buildDir.path + "/classes/$it/main" }
}
)
)
additionalSourceDirs(
files(
subprojects.map { project ->
listOf("scala", "kotlin").map { project.file("src/main/$it").absolutePath }
}
)
)
dependsOn(jacocoAggregateMerge)
}

Expand Down
2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
plugins {
plugins {
`kotlin-dsl`
}

Expand Down
11 changes: 6 additions & 5 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
@Suppress("ObjectPropertyName")
object Versions {
const val kotlin = "1.3.70"
const val kotlin = "1.5.31"
const val kotlinCoroutines = "1.5.2"
const val scalaBinary = "2.13"
const val lagom = "1.6.4" // "1.5.5"
const val play = "2.8.2"
const val hibernateVersion = "5.3.6.Final"
const val ktlint = "0.32.0"
const val `ktlint-plugin` = "8.0.0"
const val jacoco = "0.8.4"
const val kotlintest = "3.1.10"
const val ktlint = "0.41.0"
const val `ktlint-plugin` = "10.1.0"
const val jacoco = "0.8.7"
const val kotest = "4.6.0"
const val h2 = "1.4.197"
const val `nexus-staging` = "0.21.2"
const val `nexus-publish` = "0.4.0"
Expand Down
13 changes: 9 additions & 4 deletions jpa/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ plugins {

val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions.jvmTarget = "1.8"
compileKotlin.kotlinOptions.freeCompilerArgs += listOf("-Xjvm-default=enable", "-Xjsr305=strict")
compileKotlin.kotlinOptions.freeCompilerArgs += listOf("-Xjvm-default=all", "-Xjsr305=strict")

val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions.jvmTarget = "1.8"
compileTestKotlin.kotlinOptions.freeCompilerArgs += listOf("-Xjvm-default=enable", "-Xjsr305=strict")
compileTestKotlin.kotlinOptions.freeCompilerArgs += listOf("-Xjvm-default=all", "-Xjsr305=strict")

dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", Versions.kotlinCoroutines)
api(project(":play-repository-api-java"))
compileOnly("com.typesafe.play", "play-java-jpa_$scalaBinaryVersion", playVersion)
implementation("org.hibernate", "hibernate-entitymanager", Versions.hibernateVersion)

testImplementation("io.kotlintest", "kotlintest-runner-junit5", Versions.kotlintest)
testImplementation("io.kotest", "kotest-runner-junit5", Versions.kotest)
testImplementation("io.kotest", "kotest-assertions-core", Versions.kotest)
testImplementation("io.kotest", "kotest-property", Versions.kotest)
testImplementation("com.typesafe.play", "play-test_$scalaBinaryVersion", playVersion)
testImplementation("com.typesafe.play", "play-jdbc-evolutions_$scalaBinaryVersion", playVersion)
testImplementation("com.h2database", "h2", Versions.h2)
Expand All @@ -43,7 +46,9 @@ sourceSets.test {
ktlint {
version.set(Versions.ktlint)
outputToConsole.set(true)
reporters.set(setOf(ReporterType.CHECKSTYLE))
reporters {
reporter(ReporterType.CHECKSTYLE)
}
}

tasks.withType<Test> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ class DatabaseExecutionContext @Inject constructor(actorSystem: ActorSystem) : E
companion object {
const val DISPATCHER_NAME = "database.dispatcher"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.taymyr.play.repository.infrastructure.persistence

import play.db.jpa.JPAApi
import java.io.Serializable
import javax.persistence.EntityManager

abstract class JPABaseRepository<Aggregate : Any, Identity : Serializable> @JvmOverloads constructor(
protected val jpaApi: JPAApi,
protected val executionContext: DatabaseExecutionContext,
protected val clazz: Class<out Aggregate>,
protected val persistenceUnitName: String = "default"
) {
protected fun <E> transaction(function: (EntityManager) -> E): E = jpaApi.withTransaction(persistenceUnitName, function)

protected fun <E> readOnly(function: (EntityManager) -> E): E = jpaApi.withTransaction(persistenceUnitName, true, function)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,23 @@ import java.util.Optional
import java.util.Optional.ofNullable
import java.util.concurrent.CompletableFuture.supplyAsync
import java.util.concurrent.CompletionStage
import java.util.function.Supplier
import javax.persistence.EntityManager

/**
* JPA implementation of DDD repository for aggregates.
*/
abstract class JPARepository<Aggregate : Any, Identity : Serializable> @JvmOverloads constructor(
protected val jpaApi: JPAApi,
protected val executionContext: DatabaseExecutionContext,
protected val clazz: Class<out Aggregate>,
protected val persistenceUnitName: String = "default"
) : Repository<Aggregate, Identity> {

protected fun <E> transaction(function: (EntityManager) -> E): E = jpaApi.withTransaction(persistenceUnitName, function)

protected fun <E> readOnly(function: (EntityManager) -> E): E = jpaApi.withTransaction(persistenceUnitName, true, function)
jpaApi: JPAApi,
executionContext: DatabaseExecutionContext,
clazz: Class<out Aggregate>,
persistenceUnitName: String = "default"
) : JPABaseRepository<Aggregate, Identity>(jpaApi, executionContext, clazz, persistenceUnitName), Repository<Aggregate, Identity> {

protected fun <E> execute(function: (EntityManager) -> E): CompletionStage<E> =
supplyAsync(Supplier { transaction(function) }, executionContext)
supplyAsync({ transaction(function) }, executionContext)

protected fun <E> executeRO(function: (EntityManager) -> E): CompletionStage<E> =
supplyAsync(Supplier { readOnly(function) }, executionContext)
supplyAsync({ readOnly(function) }, executionContext)

protected fun <E> executeSession(function: (Session) -> E): CompletionStage<E> =
execute { em -> function.invoke(em.unwrap(Session::class.java)) }
Expand Down
Loading