From c8d12c7d1b19358d8233c78befcf8978921cf4ef Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Sun, 19 May 2024 17:34:21 +0500 Subject: [PATCH 1/9] m7l5 Gremlin/ArcadeDB --- deploy/docker-compose-arcadedb.yml | 16 ++ gradle/libs.versions.toml | 8 + .../ok-marketplace-app-ktor/build.gradle.kts | 2 + .../src/commonMain/resources/application.yaml | 15 ++ .../jvmMain/kotlin/configs/GremlinConfig.kt | 22 +++ .../kotlin/plugins/getDatabaseConf.jvm.kt | 13 ++ .../src/jvmMain/resources/application.yaml | 16 ++ .../src/commonMain/kotlin/repo/DbErrors.kt | 2 +- .../repo/exceptions/UnknownDbException.kt | 3 + .../src/test/kotlin/RepoAdCassandraTest.kt | 13 +- .../ok-marketplace-repo-gremlin/.gitignore | 1 + .../ok-marketplace-repo-gremlin/README.MD | 7 + .../build.gradle.kts | 47 +++++ .../src/main/kotlin/AdGremlinConst.kt | 16 ++ .../src/main/kotlin/AdRepoGremlin.kt | 151 +++++++++++++++ .../kotlin/exceptions/WrongEnumException.kt | 3 + .../src/main/kotlin/mappers/AddMkplAd.kt | 93 +++++++++ .../src/main/kotlin/mappers/Label.kt | 5 + .../src/test/kotlin/AdRepoGremlinTest.kt | 48 +++++ .../src/test/kotlin/ArcadeDbContainer.kt | 45 +++++ .../src/test/kotlin/SimpleTest.kt | 183 ++++++++++++++++++ .../commonTest/kotlin/AdRepoInMemoryTest.kt | 2 +- .../src/commonTest/kotlin/RepoAdSQLTest.kt | 13 +- .../src/commonTest/kotlin/SqlTestCompanion.kt | 3 +- .../src/commonMain/kotlin/RepoAdCreateTest.kt | 9 +- .../src/commonMain/kotlin/RepoAdDeleteTest.kt | 3 +- .../src/commonMain/kotlin/RepoAdReadTest.kt | 4 +- .../src/commonMain/kotlin/RepoAdSearchTest.kt | 4 +- .../src/commonMain/kotlin/RepoAdUpdateTest.kt | 3 +- ok-marketplace-be/settings.gradle.kts | 1 + 30 files changed, 723 insertions(+), 28 deletions(-) create mode 100644 deploy/docker-compose-arcadedb.yml create mode 100644 ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/configs/GremlinConfig.kt create mode 100644 ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/exceptions/UnknownDbException.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/.gitignore create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/README.MD create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/build.gradle.kts create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdGremlinConst.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdRepoGremlin.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/exceptions/WrongEnumException.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/AddMkplAd.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/Label.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/AdRepoGremlinTest.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/ArcadeDbContainer.kt create mode 100644 ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/SimpleTest.kt diff --git a/deploy/docker-compose-arcadedb.yml b/deploy/docker-compose-arcadedb.yml new file mode 100644 index 0000000..1428654 --- /dev/null +++ b/deploy/docker-compose-arcadedb.yml @@ -0,0 +1,16 @@ +version: "3.3" +services: + arcadedb: + image: "arcadedata/arcadedb:24.4.1" + ports: + - "2480:2480" + - "2424:2424" + - "8182:8182" +# volumes: +# - ./volumes/arcadedb:/home/arcadedb/databases + # Здесь можно добавить доступные через gremlin графы и псевдонимы +# - ./volumes/arcadedb:/home/arcadedb/config/gremlin-server.groovy + environment: + JAVA_OPTS: > + -Darcadedb.server.rootPassword=root_root + -Darcadedb.server.plugins=GremlinServer:com.arcadedb.server.gremlin.GremlinServerPlugin diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e8db82b..ca51b45 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,8 @@ spring-boot = "3.2.0" liquibase = "4.27.0" exposed = "0.50.0" cassandra = "4.17.0" +arcadedb = "24.4.1" +gremlin = "3.7.2" # Docker testcontainers = "1.19.7" @@ -98,6 +100,12 @@ db-cassandra-qbuilder = { module = "com.datastax.oss:java-driver-query-builder", db-cassandra-kapt = { module = "com.datastax.oss:java-driver-mapper-processor", version.ref = "cassandra" } db-cassandra-mapper = { module = "com.datastax.oss:java-driver-mapper-runtime", version.ref = "cassandra" } +# Gremlin +gdb-gremlin-driver = { module = "org.apache.tinkerpop:gremlin-driver", version.ref = "gremlin" } +gdb-arcade-engine = { module = "com.arcadedb:arcadedb-engine", version.ref = "arcadedb" } +gdb-arcade-network = { module = "com.arcadedb:arcadedb-network", version.ref = "arcadedb" } +gdb-arcade-gremlin = { module = "com.arcadedb:arcadedb-gremlin", version.ref = "arcadedb" } + # Liquidbase liquibase-core = { module = "org.liquibase:liquibase-core", version.ref = "liquibase" } liquibase-picocli = "info.picocli:picocli:4.7.5" diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts index ad9b27d..854b93d 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts @@ -119,9 +119,11 @@ kotlin { implementation(project(":ok-marketplace-api-v1-mappers")) implementation(projects.okMarketplaceRepoCassandra) + implementation(projects.okMarketplaceRepoGremlin) implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-logback") implementation(libs.testcontainers.cassandra) + implementation(libs.testcontainers.core) } } diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/resources/application.yaml b/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/resources/application.yaml index c80c65c..97e3f5d 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/resources/application.yaml +++ b/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/resources/application.yaml @@ -29,6 +29,7 @@ marketplace: repository: test: "inmemory" prod: "$DB_TYPE_PROD:inmemory" + psql: schema: public database: "$MKPLADS_DB:marketplace-ads" @@ -36,3 +37,17 @@ marketplace: port: "$MKPLADS_PORT:5432" user: "$MKPLADS_USER:postgres" password: "$MKPLADS_PASS:marketplace-pass" + + cassandra: + hosts: localhost + keyspace: test_keyspace + pass: cassandra + port: 9042 + user: cassandra + + gremlin: + host: "$DB_GREMLIN_HOST:localhost" + user: "$DB_GREMLIN_HOST:root" + password: "$DB_GREMLIN_HOST:root_root" + port: "$DB_GREMLIN_PORT:8182" + enableSsl: false diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/configs/GremlinConfig.kt b/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/configs/GremlinConfig.kt new file mode 100644 index 0000000..6e864e9 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/configs/GremlinConfig.kt @@ -0,0 +1,22 @@ +package ru.otus.otuskotlin.marketplace.app.ktor.configs + +import io.ktor.server.config.* + +data class GremlinConfig( + val host: String = "localhost", + val port: Int = 8182, + val user: String = "root", + val pass: String, + val enableSsl: Boolean = false, +) { + constructor(config: ApplicationConfig): this( + host = config.property("$PATH.host").getString(), + user = config.property("$PATH.user").getString(), + pass = config.property("$PATH.password").getString(), + enableSsl = config.property("$PATH.enableSsl").getString().toBoolean(), + ) + + companion object { + const val PATH = "${ConfigPaths.repository}.gremlin" + } +} diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/plugins/getDatabaseConf.jvm.kt b/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/plugins/getDatabaseConf.jvm.kt index 74bb890..b23c47f 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/plugins/getDatabaseConf.jvm.kt +++ b/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/kotlin/plugins/getDatabaseConf.jvm.kt @@ -3,7 +3,9 @@ package ru.otus.otuskotlin.marketplace.app.ktor.plugins import io.ktor.server.application.* import ru.otus.otuskotlin.marketplace.app.ktor.configs.CassandraConfig import ru.otus.otuskotlin.marketplace.app.ktor.configs.ConfigPaths +import ru.otus.otuskotlin.marketplace.app.ktor.configs.GremlinConfig import ru.otus.otuskotlin.marketplace.backend.repo.cassandra.RepoAdCassandra +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdRepoGremlin import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd actual fun Application.getDatabaseConf(type: AdDbType): IRepoAd { @@ -13,6 +15,7 @@ actual fun Application.getDatabaseConf(type: AdDbType): IRepoAd { "in-memory", "inmemory", "memory", "mem" -> initInMemory() "postgres", "postgresql", "pg", "sql", "psql" -> initPostgres() "cassandra", "nosql", "cass" -> initCassandra() + "arcade", "arcadedb", "graphdb", "gremlin", "g", "a" -> initGremliln() else -> throw IllegalArgumentException( "$dbSettingPath must be set in application.yml to one of: " + "'inmemory', 'postgres', 'cassandra', 'gremlin'" @@ -31,3 +34,13 @@ private fun Application.initCassandra(): IRepoAd { ) } +private fun Application.initGremliln(): IRepoAd { + val config = GremlinConfig(environment.config) + return AdRepoGremlin( + hosts = config.host, + port = config.port, + user = config.user, + pass = config.pass, + enableSsl = config.enableSsl, + ) +} diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml b/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml index 2de9178..3acf3e1 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml +++ b/ok-marketplace-be/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml @@ -40,10 +40,12 @@ ktor: # queue: mkpl-ads-v1-queue # consumerTag: "mkpl-ads-v1-consumer" # exchangeType: direct + marketplace: repository: test: "inmemory" prod: "$DB_TYPE_PROD:inmemory" + psql: schema: public database: "$MKPLADS_DB:marketplace-ads" @@ -51,3 +53,17 @@ marketplace: port: "$MKPLADS_PORT:5432" user: "$MKPLADS_USER:postgres" password: "$MKPLADS_PASS:marketplace-pass" + + cassandra: + hosts: localhost + keyspace: test_keyspace + pass: cassandra + port: 9042 + user: cassandra + + gremlin: + host: "$DB_GREMLIN_HOST:localhost" + user: "$DB_GREMLIN_HOST:root" + password: "$DB_GREMLIN_HOST:root_root" + port: "$DB_GREMLIN_PORT:8182" + enableSsl: false diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/DbErrors.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/DbErrors.kt index 7f7e5ff..9a4cd02 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/DbErrors.kt +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/DbErrors.kt @@ -58,7 +58,7 @@ fun errorEmptyLock(id: MkplAdId) = DbAdResponseErr( fun errorDb(e: RepoException) = DbAdResponseErr( errorSystem( - violationCode = "dbLockEmpty", + violationCode = "db-error", e = e ) ) diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/exceptions/UnknownDbException.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/exceptions/UnknownDbException.kt new file mode 100644 index 0000000..3929050 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/repo/exceptions/UnknownDbException.kt @@ -0,0 +1,3 @@ +package ru.otus.otuskotlin.marketplace.common.repo.exceptions + +class UnknownDbException(mes: String): RepoException(mes) diff --git a/ok-marketplace-be/ok-marketplace-repo-cassandra/src/test/kotlin/RepoAdCassandraTest.kt b/ok-marketplace-be/ok-marketplace-repo-cassandra/src/test/kotlin/RepoAdCassandraTest.kt index b2397ee..c6438fa 100644 --- a/ok-marketplace-be/ok-marketplace-repo-cassandra/src/test/kotlin/RepoAdCassandraTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-cassandra/src/test/kotlin/RepoAdCassandraTest.kt @@ -4,39 +4,38 @@ import com.benasher44.uuid.uuid4 import org.testcontainers.containers.CassandraContainer import ru.otus.otuskotlin.marketplace.backend.repo.tests.* import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized -import ru.otus.otuskotlin.marketplace.repo.common.IRepoAdInitializable import java.time.Duration class RepoAdCassandraCreateTest : RepoAdCreateTest() { - override val repo: IRepoAdInitializable = AdRepoInitialized( + override val repo = AdRepoInitialized( initObjects = initObjects, - repo = TestCompanion.repository("ks_create", uuidNew.asString()) + repo = TestCompanion.repository("ks_create", lockNew.asString()) ) } class RepoAdCassandraReadTest : RepoAdReadTest() { - override val repo: IRepoAdInitializable = AdRepoInitialized( + override val repo = AdRepoInitialized( initObjects = initObjects, repo = TestCompanion.repository("ks_read") ) } class RepoAdCassandraUpdateTest : RepoAdUpdateTest() { - override val repo: IRepoAdInitializable = AdRepoInitialized( + override val repo = AdRepoInitialized( initObjects = initObjects, repo = TestCompanion.repository("ks_update", lockNew.asString()) ) } class RepoAdCassandraDeleteTest : RepoAdDeleteTest() { - override val repo: IRepoAdInitializable = AdRepoInitialized( + override val repo = AdRepoInitialized( initObjects = initObjects, repo = TestCompanion.repository("ks_delete") ) } class RepoAdCassandraSearchTest : RepoAdSearchTest() { - override val repo: IRepoAdInitializable = AdRepoInitialized( + override val repo = AdRepoInitialized( initObjects = initObjects, repo = TestCompanion.repository("ks_search") ) diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/.gitignore b/ok-marketplace-be/ok-marketplace-repo-gremlin/.gitignore new file mode 100644 index 0000000..8297d08 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/.gitignore @@ -0,0 +1 @@ +/log/ diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/README.MD b/ok-marketplace-be/ok-marketplace-repo-gremlin/README.MD new file mode 100644 index 0000000..c0cc6bb --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/README.MD @@ -0,0 +1,7 @@ +# Модуль `ok-marketplace-repo-gremlin` + + +Модуль реализует интерфейс репозитория ArcadeDb с вариантом Apache TinkerPop Gremlin. + +Поддерживается только JVM платформа. + diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/build.gradle.kts b/ok-marketplace-be/ok-marketplace-repo-gremlin/build.gradle.kts new file mode 100644 index 0000000..aa92390 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/build.gradle.kts @@ -0,0 +1,47 @@ +import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated + +plugins { + id("build-jvm") +} + +val generatedPath = layout.buildDirectory.dir("generated/main/kotlin").get() +sourceSets { + main { + java.srcDir(generatedPath) + } +} + +dependencies { + implementation(projects.okMarketplaceCommon) + implementation(projects.okMarketplaceRepoCommon) + + implementation(libs.coroutines.core) + implementation(libs.uuid) + + implementation(libs.gdb.gremlin.driver) + implementation(libs.gdb.arcade.engine) + implementation(libs.gdb.arcade.network) + implementation(libs.gdb.arcade.gremlin) + + testImplementation(kotlin("test-junit")) + testImplementation(libs.testcontainers.core) + testImplementation(projects.okMarketplaceRepoTests) +} + +val arcadeDbVersion: String by project + +tasks { + val gradleConstants by creating { + file("$generatedPath/GradleConstants.kt").apply { + ensureParentDirsCreated() + writeText( + """ + package ru.otus.otuskotlin.marketplace.backend.repository.gremlin + + const val ARCADEDB_VERSION = "${libs.versions.arcadedb.get()}" + """.trimIndent() + ) + } + } + compileKotlin.get().dependsOn(gradleConstants) +} diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdGremlinConst.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdGremlinConst.kt new file mode 100644 index 0000000..7cd976e --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdGremlinConst.kt @@ -0,0 +1,16 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin + +object AdGremlinConst { + const val RESULT_SUCCESS = "success" + const val RESULT_LOCK_FAILURE = "lock-failure" + + const val FIELD_ID = "#id" + const val FIELD_TITLE = "title" + const val FIELD_DESCRIPTION = "description" + const val FIELD_AD_TYPE = "adType" + const val FIELD_OWNER_ID = "ownerId" + const val FIELD_VISIBILITY = "visibility" + const val FIELD_PRODUCT_ID = "productId" + const val FIELD_LOCK = "lock" + const val FIELD_TMP_RESULT = "_result" +} diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdRepoGremlin.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdRepoGremlin.kt new file mode 100644 index 0000000..c5e203c --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/AdRepoGremlin.kt @@ -0,0 +1,151 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin + +import com.benasher44.uuid.uuid4 +import org.apache.tinkerpop.gremlin.driver.Cluster +import org.apache.tinkerpop.gremlin.driver.exception.ResponseException +import org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection +import org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal +import org.apache.tinkerpop.gremlin.process.traversal.TextP +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource +import org.apache.tinkerpop.gremlin.structure.Vertex +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_AD_TYPE +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_LOCK +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_OWNER_ID +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_TITLE +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.RESULT_LOCK_FAILURE +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.mappers.* +import ru.otus.otuskotlin.marketplace.common.models.* +import ru.otus.otuskotlin.marketplace.common.repo.* +import ru.otus.otuskotlin.marketplace.common.repo.exceptions.UnknownDbException +import ru.otus.otuskotlin.marketplace.repo.common.IRepoAdInitializable +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.`__` as gr + + +class AdRepoGremlin( + private val hosts: String, + private val port: Int = 8182, + private val enableSsl: Boolean = false, + private val user: String = "root", + private val pass: String = "", + private val graph: String = "graph", // Этот граф должен быть настроен в /home/arcadedb/config/gremlin-server.groovy + val randomUuid: () -> String = { uuid4().toString() }, + initRepo: ((GraphTraversalSource) -> Unit)? = null, +) : AdRepoBase(), IRepoAd, IRepoAdInitializable { + + private val cluster by lazy { + Cluster.build().apply { + addContactPoints(*hosts.split(Regex("\\s*,\\s*")).toTypedArray()) + port(port) + credentials(user, pass) + enableSsl(enableSsl) + }.create() + } + private val g by lazy { traversal().withRemote(DriverRemoteConnection.using(cluster, graph)) } + + init { + initRepo?.also { it(g) } + } + + private fun save(ad: MkplAd): MkplAd = g.addV(ad.label()) + .addMkplAd(ad) + .listMkplAd() + .next() + ?.toMkplAd() + ?: throw RuntimeException("Cannot initialize object $ad") + + override fun save(ads: Collection): Collection = ads.map { save(it) } + + override suspend fun createAd(rq: DbAdRequest): IDbAdResponse = tryAdMethod { + val ad = rq.ad.copy(lock = MkplAdLock(randomUuid())) + g.addV(ad.label()) + .addMkplAd(ad) + .listMkplAd() + .next() + ?.toMkplAd() + ?.let { DbAdResponseOk(it) } + ?: errorDb(UnknownDbException("Db object was not returned after creation by DB: $rq")) + } + + private suspend fun checkNotFound(block: suspend () -> T?): T? = runCatching { block() }.getOrElse { e -> + if (e.cause is ResponseException) null else throw e + } + + override suspend fun readAd(rq: DbAdIdRequest): IDbAdResponse = tryAdMethod { + val key = rq.id.takeIf { it != MkplAdId.NONE }?.asString() ?: return@tryAdMethod errorEmptyId + val adResp = checkNotFound { g.V(key).listMkplAd().next()?.toMkplAd() } + when { + adResp == null -> errorNotFound(rq.id) + else -> DbAdResponseOk(adResp) + } + } + + override suspend fun updateAd(rq: DbAdRequest): IDbAdResponse = tryAdMethod { + val key = rq.ad.id.takeIf { it != MkplAdId.NONE }?.asString() ?: return@tryAdMethod errorEmptyId + val oldLock = rq.ad.lock.takeIf { it != MkplAdLock.NONE } ?: return@tryAdMethod errorEmptyLock(rq.ad.id) + val newLock = MkplAdLock(randomUuid()) + val newAd = rq.ad.copy(lock = newLock) + val adResp = checkNotFound { + g + .V(key) + .`as`("a") + .choose( + gr.select("a") + .values(FIELD_LOCK) + .`is`(oldLock.asString()), + gr.select("a").addMkplAd(newAd).listMkplAd(), + gr.select("a").listMkplAd(result = RESULT_LOCK_FAILURE) + ) + .next() + ?.toMkplAd() + } + when { + adResp == null -> errorNotFound(rq.ad.id) + adResp.lock == newAd.lock -> DbAdResponseOk(adResp) + else -> errorRepoConcurrency(adResp, oldLock) + } + } + + override suspend fun deleteAd(rq: DbAdIdRequest): IDbAdResponse = tryAdMethod { + val key = rq.id.takeIf { it != MkplAdId.NONE }?.asString() ?: return@tryAdMethod errorEmptyId + val oldLock = rq.lock.takeIf { it != MkplAdLock.NONE } ?: return@tryAdMethod errorEmptyLock(rq.id) + val res = checkNotFound { + g + .V(key) + .`as`("a") + .choose( + gr.select("a") + .values(FIELD_LOCK) + .`is`(oldLock.asString()), + gr.select("a") + .sideEffect(gr.drop()) + .listMkplAd(), + gr.select("a") + .listMkplAd(result = RESULT_LOCK_FAILURE) + ) + .next() + } + val adResp = res?.toMkplAd() + when { + adResp == null -> errorNotFound(rq.id) + res.getFailureMarker() == RESULT_LOCK_FAILURE -> errorRepoConcurrency(adResp, oldLock) + else -> DbAdResponseOk(adResp) + } + } + + /** + * Поиск объявлений по фильтру + * Если в фильтре не установлен какой-либо из параметров - по нему фильтрация не идет + */ + override suspend fun searchAd(rq: DbAdFilterRequest): IDbAdsResponse = tryAdsMethod { + g.V() + .apply { rq.ownerId.takeIf { it != MkplUserId.NONE }?.also { has(FIELD_OWNER_ID, it.asString()) } } + .apply { rq.dealSide.takeIf { it != MkplDealSide.NONE }?.also { has(FIELD_AD_TYPE, it.name) } } + .apply { + rq.titleFilter.takeIf { it.isNotBlank() }?.also { has(FIELD_TITLE, TextP.containing(it)) } + } + .listMkplAd() + .toList() + .let { result -> DbAdsResponseOk(data = result.map { it.toMkplAd() }) } + } + +} diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/exceptions/WrongEnumException.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/exceptions/WrongEnumException.kt new file mode 100644 index 0000000..0114d59 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/exceptions/WrongEnumException.kt @@ -0,0 +1,3 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin.exceptions + +class WrongEnumException(message: String) : Exception(message) diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/AddMkplAd.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/AddMkplAd.kt new file mode 100644 index 0000000..fc66411 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/AddMkplAd.kt @@ -0,0 +1,93 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin.mappers + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.`__` as gr +import org.apache.tinkerpop.gremlin.structure.Vertex +import org.apache.tinkerpop.gremlin.structure.VertexProperty +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_AD_TYPE +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_DESCRIPTION +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_ID +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_LOCK +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_OWNER_ID +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_PRODUCT_ID +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_TITLE +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_TMP_RESULT +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.FIELD_VISIBILITY +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.AdGremlinConst.RESULT_SUCCESS +import ru.otus.otuskotlin.marketplace.backend.repository.gremlin.exceptions.WrongEnumException +import ru.otus.otuskotlin.marketplace.common.models.* + +fun GraphTraversal.addMkplAd(ad: MkplAd): GraphTraversal = + this + .property(VertexProperty.Cardinality.single, FIELD_TITLE, ad.title.takeIf { it.isNotBlank() }) + .property(VertexProperty.Cardinality.single, FIELD_DESCRIPTION, ad.description.takeIf { it.isNotBlank() }) + .property(VertexProperty.Cardinality.single, FIELD_LOCK, ad.lock.takeIf { it != MkplAdLock.NONE }?.asString()) + .property( + VertexProperty.Cardinality.single, + FIELD_OWNER_ID, + ad.ownerId.asString().takeIf { it.isNotBlank() }) // здесь можно сделать ссылку на объект владельца + .property(VertexProperty.Cardinality.single, FIELD_AD_TYPE, ad.adType.takeIf { it != MkplDealSide.NONE }?.name) + .property( + VertexProperty.Cardinality.single, + FIELD_VISIBILITY, + ad.visibility.takeIf { it != MkplVisibility.NONE }?.name + ) + .property( + VertexProperty.Cardinality.single, + FIELD_PRODUCT_ID, + ad.productId.takeIf { it != MkplProductId.NONE }?.asString() + ) + +fun GraphTraversal.listMkplAd(result: String = RESULT_SUCCESS): GraphTraversal> = + project( + FIELD_ID, + FIELD_OWNER_ID, + FIELD_LOCK, + FIELD_TITLE, + FIELD_DESCRIPTION, + FIELD_AD_TYPE, + FIELD_VISIBILITY, + FIELD_PRODUCT_ID, + FIELD_TMP_RESULT, + ) + .by(gr.id()) + .by(FIELD_OWNER_ID) +// .by(gr.inE("Owns").outV().id()) + .by(FIELD_LOCK) + .by(FIELD_TITLE) + .by(FIELD_DESCRIPTION) + .by(FIELD_AD_TYPE) + .by(FIELD_VISIBILITY) + .by(FIELD_PRODUCT_ID) + .by(gr.constant(result)) +// .by(elementMap>()) + +fun Map.toMkplAd(): MkplAd = MkplAd( + id = (this[FIELD_ID] as? String)?.let { MkplAdId(it) } ?: MkplAdId.NONE, + ownerId = (this[FIELD_OWNER_ID] as? String)?.let { MkplUserId(it) } ?: MkplUserId.NONE, + lock = (this[FIELD_LOCK] as? String)?.let { MkplAdLock(it) } ?: MkplAdLock.NONE, + title = (this[FIELD_TITLE] as? String) ?: "", + description = (this[FIELD_DESCRIPTION] as? String) ?: "", + adType = when (val value = this[FIELD_AD_TYPE] as? String) { + MkplDealSide.SUPPLY.name -> MkplDealSide.SUPPLY + MkplDealSide.DEMAND.name -> MkplDealSide.DEMAND + null -> MkplDealSide.NONE + else -> throw WrongEnumException( + "Cannot convert object from DB. " + + "adType = '$value' cannot be converted to ${MkplDealSide::class}" + ) + }, + visibility = when (val value = this[FIELD_VISIBILITY]) { + MkplVisibility.VISIBLE_PUBLIC.name -> MkplVisibility.VISIBLE_PUBLIC + MkplVisibility.VISIBLE_TO_GROUP.name -> MkplVisibility.VISIBLE_TO_GROUP + MkplVisibility.VISIBLE_TO_OWNER.name -> MkplVisibility.VISIBLE_TO_OWNER + null -> MkplVisibility.NONE + else -> throw WrongEnumException( + "Cannot convert object from DB. " + + "visibility = '$value' cannot be converted to ${MkplVisibility::class}" + ) + }, + productId = (this[FIELD_PRODUCT_ID] as? String)?.let { MkplProductId(it) } ?: MkplProductId.NONE, +) + +fun Map.getFailureMarker(): String? = this[FIELD_TMP_RESULT] as? String diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/Label.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/Label.kt new file mode 100644 index 0000000..757542a --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/main/kotlin/mappers/Label.kt @@ -0,0 +1,5 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin.mappers + +import ru.otus.otuskotlin.marketplace.common.models.MkplAd + +fun MkplAd.label(): String? = this::class.simpleName diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/AdRepoGremlinTest.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/AdRepoGremlinTest.kt new file mode 100644 index 0000000..280af60 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/AdRepoGremlinTest.kt @@ -0,0 +1,48 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin + +import ru.otus.otuskotlin.marketplace.backend.repo.tests.* +import ru.otus.otuskotlin.marketplace.common.models.MkplAd +import ru.otus.otuskotlin.marketplace.common.models.MkplAdId +import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized + +class RepoAdGremlinCreateTest : RepoAdCreateTest() { + override val repo = AdRepoInitialized( + initObjects = initObjects, + repo = ArcadeDbContainer.repository("test_create", lockNew.asString()) + ) +} + +class RepoAdGremlinReadTest : RepoAdReadTest() { + override val repo = AdRepoInitialized( + initObjects = initObjects, + repo = ArcadeDbContainer.repository("test_read") + ) + override val readSucc = repo.initializedObjects[0] +} + +class RepoAdGremlinUpdateTest : RepoAdUpdateTest() { + override val repo = AdRepoInitialized( + initObjects = initObjects, + repo = ArcadeDbContainer.repository("test_update", lockNew.asString()) + ) + override val updateSucc: MkplAd by lazy { repo.initializedObjects[0] } + override val updateConc: MkplAd by lazy { repo.initializedObjects[1] } +} + +class RepoAdGremlinDeleteTest : RepoAdDeleteTest() { + override val repo = AdRepoInitialized( + initObjects = initObjects, + repo = ArcadeDbContainer.repository("test_delete") + ) + override val deleteSucc: MkplAd by lazy { repo.initializedObjects[0] } + override val deleteConc: MkplAd by lazy { repo.initializedObjects[1] } + override val notFoundId: MkplAdId = MkplAdId("#3100:0") +} + +class RepoAdGremlinSearchTest : RepoAdSearchTest() { + override val repo = AdRepoInitialized( + initObjects = initObjects, + repo = ArcadeDbContainer.repository("test_search") + ) + override val initializedObjects: List = repo.initializedObjects +} diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/ArcadeDbContainer.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/ArcadeDbContainer.kt new file mode 100644 index 0000000..c26b0ac --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/ArcadeDbContainer.kt @@ -0,0 +1,45 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin + +import com.benasher44.uuid.uuid4 +import org.testcontainers.containers.GenericContainer +import org.testcontainers.containers.wait.strategy.Wait +import org.testcontainers.utility.DockerImageName +import java.time.Duration + +object ArcadeDbContainer { + val username: String = "root" + val password: String = "root_root" + val container by lazy { + GenericContainer(DockerImageName.parse("arcadedata/arcadedb:${ARCADEDB_VERSION}")).apply { + withExposedPorts(2480, 2424, 8182) + withEnv( + "JAVA_OPTS", "-Darcadedb.server.rootPassword=$password " + + "-Darcadedb.server.plugins=GremlinServer:com.arcadedb.server.gremlin.GremlinServerPlugin" + ) + waitingFor(Wait.forLogMessage(".*ArcadeDB Server started.*\\n", 1)) + withStartupTimeout(Duration.ofMinutes(5)) + start() + println("ARCADE: http://${host}:${getMappedPort(2480)}") + println("ARCADE: http://${host}:${getMappedPort(2424)}") + println(this.logs) + println("RUNNING?: ${this.isRunning}") + } + } + + fun repository( + @Suppress("UNUSED_PARAMETER") db: String, + uuid: String? = null + ): AdRepoGremlin { + return AdRepoGremlin( + hosts = container.host, + port = container.getMappedPort(8182), + enableSsl = false, + user = username, + pass = password, + graph = "graph", // сюда должно бы встать значение аргумента db, но для этого нужно настраивать БД + randomUuid = uuid?.let { { uuid } } ?: { uuid4().toString() }, + initRepo = { g -> g.V().drop().iterate() }, + ) + } + +} diff --git a/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/SimpleTest.kt b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/SimpleTest.kt new file mode 100644 index 0000000..0df0e7c --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-repo-gremlin/src/test/kotlin/SimpleTest.kt @@ -0,0 +1,183 @@ +package ru.otus.otuskotlin.marketplace.backend.repository.gremlin + +import com.arcadedb.gremlin.ArcadeGraphFactory +import com.arcadedb.query.sql.executor.ResultSet +import com.arcadedb.remote.RemoteDatabase +import com.arcadedb.remote.RemoteServer +import org.apache.tinkerpop.gremlin.driver.AuthProperties +import org.apache.tinkerpop.gremlin.driver.Cluster +import org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection +import org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal +import org.apache.tinkerpop.gremlin.structure.Vertex +import org.apache.tinkerpop.gremlin.structure.VertexProperty +import org.junit.Test +import kotlin.test.Ignore +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.`__` as bs + +/** + * Для запуска тестов этого файла требуется запустить локальный экземпляр ArcadeDB + * Можно использовать файл /deploy/docker-compose-arcadedb.yml + */ +@Ignore("Тест для экспериментов") +class SimpleTest { + private val host: String = "localhost" + private val user: String = "root" + private val pass: String = "root_root" + + private val dbName: String = "graph" + + // private val dbName: String = "mkpl" // Этот граф должен быть настроен в /home/arcadedb/config/gremlin-server.groovy + private val aPort: Int = 2480 + private val gPort: Int = 8182 + + /** + * Пример запроса без использования библиотеки Gremlin. + * Дает доступ к другим графам данных без ограничений. + */ + @Test + fun arcadeSimple() { + val server = RemoteServer(host, aPort, user, pass) + if (!server.exists(dbName)) { + server.create(dbName) + } + assert(server.databases().contains(dbName)) + RemoteDatabase(host, aPort, dbName, user, pass).use { db -> + val res: ResultSet = db.command("gremlin", "g.V().elementMap().toList()") + res.forEachRemaining { block -> + val vxList = block.getProperty>>("result") + vxList.forEach { vx -> + println("V:") + vx.forEach { p -> + println(" ${p.key}=${p.value}") + } + } + } + } + } + + /** + * Организуем соединение с БД средствами ArcadeDB, выполняем запросы с помощью Gremlin + * Доступ по умолчанию только к graph. + */ + @Test + fun arcadeConnection() { + ArcadeGraphFactory.withRemote(host, aPort, dbName, user, pass).use { pool -> + pool.get().use { graph -> + val userId = graph.traversal() + .addV("User") + .property(VertexProperty.Cardinality.single, "name", "Evan") + .next() + .id() + println("UserID: $userId") + } + } + } + + /** + * Работа только средствами Gremlin + */ + @Test + fun gremlinConnection() { + val authProp = AuthProperties().apply { + with(AuthProperties.Property.USERNAME, user) + with(AuthProperties.Property.PASSWORD, pass) + } + val cluster = Cluster.build() + .addContactPoints(host) + .port(gPort) + .authProperties(authProp) + .create() + traversal() + // Этот граф должен быть указан в /home/arcadedb/config/gremblin-server.groovy + .withRemote(DriverRemoteConnection.using(cluster, "graph")) + .use { g -> + val userId = g + .addV("User") + .property(VertexProperty.Cardinality.single, "name", "Evan") + .next() + .id() + println("UserID: $userId") + } + } + + /** + * Демонстрация project + */ + @Test + fun projectTest() { + val cluster = Cluster.build().apply { + addContactPoints(host) + port(gPort) + credentials(user, pass) + }.create() + traversal() + .withRemote(DriverRemoteConnection.using(cluster)).use { g -> + val x = g.V().hasLabel("Test") + .toList() + println("CONTENT: ${x}") + + val y = g.V().hasLabel("Test").`as`("a") + .project("lock", "ownerId", "z") + .by("lock") + // Извлекаем ID связанного объекта (связь через грань Owns + .by(bs.inE("Owns").outV().id()) + .by(bs.elementMap>()) + .toList() + println("CONTENT: $y") + } + } + + /** + * Демонстрация некоторых возможностей Gremlin + */ + @Test + fun gremlinExamples() { + val cluster = Cluster.build().apply { + addContactPoints(host) + port(gPort) + credentials(user, pass) + }.create() + traversal().withRemote(DriverRemoteConnection.using(cluster)).use { g -> + // Создаем узел + val userId = g + .addV("User") + .property("name", "Ivan") + .next() + .id() + println("UserID: $userId") + + // Создаем узел и привязываем его к предыдущему через связь Owns + val id = g + .addV("Test") + .`as`("a") + .property("lock", "111") + .addE("Owns") + .from(bs.V(userId)) + .select("a") + .next() + .id() + println("ID: $id") + + // Это поиск связи по ID + val owner = g + .V(userId) + .outE("Owns") + .where(bs.inV().id().`is`(id)) + .toList() + println("OWNER: $owner") + + // Запрос с условием choose + val n = g + .V(id) + .`as`("a") + .choose( + bs.select("a") + .values("lock") + .`is`("1112"), + bs.select("a").drop().inject("success"), + bs.constant("lock-failure") + ).toList() + println("YYY: $n") + } + } +} diff --git a/ok-marketplace-be/ok-marketplace-repo-inmemory/src/commonTest/kotlin/AdRepoInMemoryTest.kt b/ok-marketplace-be/ok-marketplace-repo-inmemory/src/commonTest/kotlin/AdRepoInMemoryTest.kt index fd01431..73ca674 100644 --- a/ok-marketplace-be/ok-marketplace-repo-inmemory/src/commonTest/kotlin/AdRepoInMemoryTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-inmemory/src/commonTest/kotlin/AdRepoInMemoryTest.kt @@ -4,7 +4,7 @@ import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory class AdRepoInMemoryCreateTest : RepoAdCreateTest() { override val repo = AdRepoInitialized( - AdRepoInMemory(randomUuid = { uuidNew.asString() }), + AdRepoInMemory(randomUuid = { lockNew.asString() }), initObjects = initObjects, ) } diff --git a/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/RepoAdSQLTest.kt b/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/RepoAdSQLTest.kt index 2c129c2..2e149c3 100644 --- a/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/RepoAdSQLTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/RepoAdSQLTest.kt @@ -3,7 +3,6 @@ package ru.otus.otuskotlin.marketplace.backend.repo.postgresql import ru.otus.otuskotlin.marketplace.backend.repo.tests.* import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized -import ru.otus.otuskotlin.marketplace.repo.common.IRepoAdInitializable import kotlin.test.AfterTest private fun IRepoAd.clear() { @@ -12,22 +11,22 @@ private fun IRepoAd.clear() { } class RepoAdSQLCreateTest : RepoAdCreateTest() { - override val repo: IRepoAdInitializable = SqlTestCompanion.repoUnderTestContainer( + override val repo = SqlTestCompanion.repoUnderTestContainer( initObjects, - randomUuid = { uuidNew.asString() }, + randomUuid = { lockNew.asString() }, ) @AfterTest fun tearDown() = repo.clear() } class RepoAdSQLReadTest : RepoAdReadTest() { - override val repo: IRepoAd = SqlTestCompanion.repoUnderTestContainer(initObjects) + override val repo = SqlTestCompanion.repoUnderTestContainer(initObjects) @AfterTest fun tearDown() = repo.clear() } class RepoAdSQLUpdateTest : RepoAdUpdateTest() { - override val repo: IRepoAd = SqlTestCompanion.repoUnderTestContainer( + override val repo = SqlTestCompanion.repoUnderTestContainer( initObjects, randomUuid = { lockNew.asString() }, ) @@ -38,13 +37,13 @@ class RepoAdSQLUpdateTest : RepoAdUpdateTest() { } class RepoAdSQLDeleteTest : RepoAdDeleteTest() { - override val repo: IRepoAd = SqlTestCompanion.repoUnderTestContainer(initObjects) + override val repo = SqlTestCompanion.repoUnderTestContainer(initObjects) @AfterTest fun tearDown() = repo.clear() } class RepoAdSQLSearchTest : RepoAdSearchTest() { - override val repo: IRepoAd = SqlTestCompanion.repoUnderTestContainer(initObjects) + override val repo = SqlTestCompanion.repoUnderTestContainer(initObjects) @AfterTest fun tearDown() = repo.clear() } diff --git a/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/SqlTestCompanion.kt b/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/SqlTestCompanion.kt index 11a0539..3341360 100644 --- a/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/SqlTestCompanion.kt +++ b/ok-marketplace-be/ok-marketplace-repo-postgres/src/commonTest/kotlin/SqlTestCompanion.kt @@ -3,7 +3,6 @@ package ru.otus.otuskotlin.marketplace.backend.repo.postgresql import com.benasher44.uuid.uuid4 import ru.otus.otuskotlin.marketplace.common.models.MkplAd import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized -import ru.otus.otuskotlin.marketplace.repo.common.IRepoAdInitializable object SqlTestCompanion { private const val HOST = "localhost" @@ -14,7 +13,7 @@ object SqlTestCompanion { fun repoUnderTestContainer( initObjects: Collection = emptyList(), randomUuid: () -> String = { uuid4().toString() }, - ): IRepoAdInitializable = AdRepoInitialized( + ) = AdRepoInitialized( repo = RepoAdSql( SqlProperties( host = HOST, diff --git a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdCreateTest.kt b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdCreateTest.kt index 6e5747b..011a7e6 100644 --- a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdCreateTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdCreateTest.kt @@ -3,13 +3,13 @@ package ru.otus.otuskotlin.marketplace.backend.repo.tests import ru.otus.otuskotlin.marketplace.common.models.* import ru.otus.otuskotlin.marketplace.common.repo.DbAdRequest import ru.otus.otuskotlin.marketplace.common.repo.DbAdResponseOk -import ru.otus.otuskotlin.marketplace.repo.common.IRepoAdInitializable +import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized import kotlin.test.* abstract class RepoAdCreateTest { - abstract val repo: IRepoAdInitializable - protected open val uuidNew = MkplAdId("10000000-0000-0000-0000-000000000001") + abstract val repo: AdRepoInitialized + protected open val lockNew = MkplAdLock("20000000-0000-0000-0000-000000000002") private val createObj = MkplAd( title = "create object", @@ -24,7 +24,8 @@ abstract class RepoAdCreateTest { val result = repo.createAd(DbAdRequest(createObj)) val expected = createObj assertIs(result) - assertEquals(uuidNew, result.data.id) + assertNotEquals(MkplAdId.NONE, result.data.id) + assertEquals(lockNew, result.data.lock) assertEquals(expected.title, result.data.title) assertEquals(expected.description, result.data.description) assertEquals(expected.adType, result.data.adType) diff --git a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdDeleteTest.kt b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdDeleteTest.kt index 99c42b7..942a6c7 100644 --- a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdDeleteTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdDeleteTest.kt @@ -3,13 +3,14 @@ package ru.otus.otuskotlin.marketplace.backend.repo.tests import ru.otus.otuskotlin.marketplace.common.models.MkplAd import ru.otus.otuskotlin.marketplace.common.models.MkplAdId import ru.otus.otuskotlin.marketplace.common.repo.* +import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertNotNull abstract class RepoAdDeleteTest { - abstract val repo: IRepoAd + abstract val repo: AdRepoInitialized protected open val deleteSucc = initObjects[0] protected open val deleteConc = initObjects[1] protected open val notFoundId = MkplAdId("ad-repo-delete-notFound") diff --git a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdReadTest.kt b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdReadTest.kt index d1b168c..0536251 100644 --- a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdReadTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdReadTest.kt @@ -6,14 +6,14 @@ import ru.otus.otuskotlin.marketplace.common.models.MkplError import ru.otus.otuskotlin.marketplace.common.repo.DbAdIdRequest import ru.otus.otuskotlin.marketplace.common.repo.DbAdResponseErr import ru.otus.otuskotlin.marketplace.common.repo.DbAdResponseOk -import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd +import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs abstract class RepoAdReadTest { - abstract val repo: IRepoAd + abstract val repo: AdRepoInitialized protected open val readSucc = initObjects[0] @Test diff --git a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdSearchTest.kt b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdSearchTest.kt index 29e70b4..aa412a9 100644 --- a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdSearchTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdSearchTest.kt @@ -5,14 +5,14 @@ import ru.otus.otuskotlin.marketplace.common.models.MkplDealSide import ru.otus.otuskotlin.marketplace.common.models.MkplUserId import ru.otus.otuskotlin.marketplace.common.repo.DbAdFilterRequest import ru.otus.otuskotlin.marketplace.common.repo.DbAdsResponseOk -import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd +import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs abstract class RepoAdSearchTest { - abstract val repo: IRepoAd + abstract val repo: AdRepoInitialized protected open val initializedObjects: List = initObjects diff --git a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdUpdateTest.kt b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdUpdateTest.kt index ceac5a5..b8a4c56 100644 --- a/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdUpdateTest.kt +++ b/ok-marketplace-be/ok-marketplace-repo-tests/src/commonMain/kotlin/RepoAdUpdateTest.kt @@ -2,13 +2,14 @@ package ru.otus.otuskotlin.marketplace.backend.repo.tests import ru.otus.otuskotlin.marketplace.common.models.* import ru.otus.otuskotlin.marketplace.common.repo.* +import ru.otus.otuskotlin.marketplace.repo.common.AdRepoInitialized import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs abstract class RepoAdUpdateTest { - abstract val repo: IRepoAd + abstract val repo: AdRepoInitialized protected open val updateSucc = initObjects[0] protected open val updateConc = initObjects[1] protected val updateIdNotFound = MkplAdId("ad-repo-update-not-found") diff --git a/ok-marketplace-be/settings.gradle.kts b/ok-marketplace-be/settings.gradle.kts index 914f43b..1bf5c7c 100644 --- a/ok-marketplace-be/settings.gradle.kts +++ b/ok-marketplace-be/settings.gradle.kts @@ -51,4 +51,5 @@ include(":ok-marketplace-repo-stubs") include(":ok-marketplace-repo-tests") include(":ok-marketplace-repo-postgres") include(":ok-marketplace-repo-cassandra") +include(":ok-marketplace-repo-gremlin") From 2dc560d77c78225c3584a1791789c2f895945303 Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Sun, 19 May 2024 23:31:19 +0500 Subject: [PATCH 2/9] m8l1 StateMachine --- .../ok-marketplace-app-ktor/build.gradle.kts | 2 + .../kotlin/plugins/InitAppSettings.kt | 2 + .../build.gradle.kts | 2 + .../src/main/kotlin/config/AdConfig.kt | 2 + .../ok-marketplace-biz-state/build.gradle.kts | 37 +++++++++++ .../kotlin/SMAdStateResolverDefault.kt | 64 +++++++++++++++++++ .../src/commonTest/kotlin/SMAdStateTest.kt | 37 +++++++++++ .../kotlin/statemachine/ComputeAdState.kt | 37 +++++++++++ .../src/commonMain/kotlin/MkplCorSettings.kt | 2 + .../src/commonMain/kotlin/models/MkplAd.kt | 11 +++- .../kotlin/statemachine/ISMAdStateResolver.kt | 11 ++++ .../kotlin/statemachine/SMAdSignal.kt | 9 +++ .../kotlin/statemachine/SMAdStates.kt | 16 +++++ .../kotlin/statemachine/SMTransition.kt | 10 +++ ok-marketplace-be/settings.gradle.kts | 1 + 15 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 ok-marketplace-be/ok-marketplace-biz-state/build.gradle.kts create mode 100644 ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin/SMAdStateResolverDefault.kt create mode 100644 ok-marketplace-be/ok-marketplace-biz-state/src/commonTest/kotlin/SMAdStateTest.kt create mode 100644 ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt create mode 100644 ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt create mode 100644 ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt create mode 100644 ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdStates.kt create mode 100644 ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMTransition.kt diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts index 854b93d..ceacff6 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts @@ -81,6 +81,8 @@ kotlin { implementation(projects.okMarketplaceRepoInmemory) implementation(projects.okMarketplaceRepoPostgres) + implementation(projects.okMarketplaceBizState) + // logging implementation(project(":ok-marketplace-api-log1")) implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common") diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt b/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt index 70f10be..4df8f0e 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt +++ b/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt @@ -5,6 +5,7 @@ import ru.otus.otuskotlin.marketplace.app.ktor.MkplAppSettings import ru.otus.otuskotlin.marketplace.app.ktor.base.KtorWsSessionRepo import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.statemachine.SMAdStateResolverDefault import ru.otus.otuskotlin.marketplace.common.MkplCorSettings fun Application.initAppSettings(): MkplAppSettings { @@ -14,6 +15,7 @@ fun Application.initAppSettings(): MkplAppSettings { repoTest = getDatabaseConf(AdDbType.TEST), repoProd = getDatabaseConf(AdDbType.PROD), repoStub = AdRepoStub(), + stateMachine = SMAdStateResolverDefault(), ) return MkplAppSettings( appUrls = environment.config.propertyOrNull("ktor.urls")?.getList() ?: emptyList(), diff --git a/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts index 7f316a6..9f4a25f 100644 --- a/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { testImplementation(projects.okMarketplaceRepoCommon) testImplementation(projects.okMarketplaceStubs) + implementation(projects.okMarketplaceBizState) + // tests testImplementation(kotlin("test-junit5")) testImplementation(libs.spring.test) diff --git a/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt b/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt index 3e18e56..d819895 100644 --- a/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt +++ b/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt @@ -10,6 +10,7 @@ import ru.otus.otuskotlin.markeplace.app.spring.base.SpringWsSessionRepo import ru.otus.otuskotlin.marketplace.backend.repo.postgresql.RepoAdSql import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.statemachine.SMAdStateResolverDefault import ru.otus.otuskotlin.marketplace.common.MkplCorSettings import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider @@ -47,6 +48,7 @@ class AdConfig(val postgresConfig: AdConfigPostgres) { repoTest = testRepo(), repoProd = prodRepo(), repoStub = stubRepo(), + stateMachine = SMAdStateResolverDefault() ) @Bean diff --git a/ok-marketplace-be/ok-marketplace-biz-state/build.gradle.kts b/ok-marketplace-be/ok-marketplace-biz-state/build.gradle.kts new file mode 100644 index 0000000..2606985 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-biz-state/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("build-kmp") +} + +kotlin { + sourceSets { + all { languageSettings.optIn("kotlin.RequiresOptIn") } + + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(libs.cor) + + implementation(project(":ok-marketplace-common")) + } + } + commonTest { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + + api(libs.coroutines.test) + } + } + jvmMain { + dependencies { + implementation(kotlin("stdlib-jdk8")) + } + } + jvmTest { + dependencies { + implementation(kotlin("test-junit")) + } + } + } +} diff --git a/ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin/SMAdStateResolverDefault.kt b/ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin/SMAdStateResolverDefault.kt new file mode 100644 index 0000000..f135a55 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin/SMAdStateResolverDefault.kt @@ -0,0 +1,64 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine + +import ru.otus.otuskotlin.marketplace.common.statemachine.ISMAdStateResolver +import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdSignal +import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates +import ru.otus.otuskotlin.marketplace.common.statemachine.SMTransition +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds + +class SMAdStateResolverDefault: ISMAdStateResolver { + override fun resolve(signal: SMAdSignal): SMTransition { + require(signal.duration >= 0.milliseconds) { "Publication duration cannot be negative" } + require(signal.views >= 0) { "View count cannot be negative" } + val sig = Sig( + st = signal.state, + dur = SMDurs.entries.first { signal.duration >= it.min && signal.duration < it.max }, + vws = SMViews.entries.first { signal.views >= it.min && signal.views < it.max }, + ) + + return TR_MX[sig] ?: SMTransition.ERROR + } + + companion object { + private enum class SMDurs(val min: Duration, val max: Duration) { + D_NEW(0.seconds, 3.days), + D_ACT(3.days, 14.days), + D_OLD(14.days, Int.MAX_VALUE.seconds), + } + private enum class SMViews(val min: Int, val max: Int) { FEW(0, 30), MODER(30, 100), LARGE(100, Int.MAX_VALUE) } + private data class Sig( + val st: SMAdStates, + val dur: SMDurs, + val vws: SMViews, + ) + + private val TR_MX = mapOf( + Sig(SMAdStates.NEW, SMDurs.D_NEW, SMViews.FEW) to SMTransition(SMAdStates.NEW, "Новое без изменений"), + Sig(SMAdStates.NEW, SMDurs.D_ACT, SMViews.FEW) to SMTransition(SMAdStates.ACTUAL, "Вышло время, перевод из нового в актуальное"), + Sig(SMAdStates.NEW, SMDurs.D_NEW, SMViews.MODER) to SMTransition( + SMAdStates.HIT, + "Много просмотров, стало хитом" + ), + Sig(SMAdStates.NEW, SMDurs.D_NEW, SMViews.LARGE) to SMTransition( + SMAdStates.HIT, + "Очень много просмотров, стало хитом" + ), + Sig(SMAdStates.HIT, SMDurs.D_NEW, SMViews.MODER) to SMTransition(SMAdStates.HIT, "Остается хитом"), + Sig(SMAdStates.HIT, SMDurs.D_ACT, SMViews.MODER) to SMTransition( + SMAdStates.ACTUAL, + "Время вышло, хит утих, становится актуальным" + ), + Sig(SMAdStates.HIT, SMDurs.D_ACT, SMViews.LARGE) to SMTransition( + SMAdStates.ACTUAL, + "Время вышло, хит становится популярным" + ), + Sig(SMAdStates.NEW, SMDurs.D_OLD, SMViews.FEW) to SMTransition( + SMAdStates.OLD, + "Устарело, просмотров мало, непопулярное и старое объявление" + ), + ) + } +} diff --git a/ok-marketplace-be/ok-marketplace-biz-state/src/commonTest/kotlin/SMAdStateTest.kt b/ok-marketplace-be/ok-marketplace-biz-state/src/commonTest/kotlin/SMAdStateTest.kt new file mode 100644 index 0000000..1cedf4c --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-biz-state/src/commonTest/kotlin/SMAdStateTest.kt @@ -0,0 +1,37 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine + +import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdSignal +import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.days + +class SMAdStateTest { + + @Test + fun new2actual() { + val machine = SMAdStateResolverDefault() + val signal = SMAdSignal( + state = SMAdStates.NEW, + duration = 4.days, + views = 20, + ) + val transition = machine.resolve(signal) + assertEquals(SMAdStates.ACTUAL, transition.state) + assertContains(transition.description, "актуальное", ignoreCase = true) + } + + @Test + fun new2hit() { + val machine = SMAdStateResolverDefault() + val signal = SMAdSignal( + state = SMAdStates.NEW, + duration = 2.days, + views = 101, + ) + val transition = machine.resolve(signal) + assertEquals(SMAdStates.HIT, transition.state) + assertContains(transition.description, "Очень", ignoreCase = true) + } +} diff --git a/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt new file mode 100644 index 0000000..dbcea06 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt @@ -0,0 +1,37 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.NONE +import ru.otus.otuskotlin.marketplace.common.models.MkplState +import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdSignal +import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import kotlin.reflect.KClass + +private val clazz: KClass<*> = ICorChainDsl::computeAdState::class +fun ICorChainDsl.computeAdState(title: String) = worker { + this.title = title + this.description = "Вычисление состояния объявления" + on { state == MkplState.RUNNING } + handle { + val machine = this.corSettings.stateMachine + val log = corSettings.loggerProvider.logger(clazz) + val timeNow = Clock.System.now() + val ad = adValidated + val prevState = ad.adState + val timePublished = ad.timePublished.takeIf { it != Instant.NONE } ?: timeNow + val signal = SMAdSignal( + state = prevState.takeIf { it != SMAdStates.NONE } ?: SMAdStates.NEW, + duration = timeNow - timePublished, + views = ad.views, + ) + val transition = machine.resolve(signal) + if (transition.state != prevState) { + log.info("New ad state transition: ${transition.description}") + } + ad.adState = transition.state + } +} diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt index 4ca406e..5bb3c21 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt @@ -1,6 +1,7 @@ package ru.otus.otuskotlin.marketplace.common import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd +import ru.otus.otuskotlin.marketplace.common.statemachine.ISMAdStateResolver import ru.otus.otuskotlin.marketplace.common.ws.IMkplWsSessionRepo import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider @@ -10,6 +11,7 @@ data class MkplCorSettings( val repoStub: IRepoAd = IRepoAd.NONE, val repoTest: IRepoAd = IRepoAd.NONE, val repoProd: IRepoAd = IRepoAd.NONE, + val stateMachine: ISMAdStateResolver = ISMAdStateResolver.NONE ) { companion object { val NONE = MkplCorSettings() diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt index dfd5e36..21ff626 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt @@ -1,5 +1,9 @@ package ru.otus.otuskotlin.marketplace.common.models +import kotlinx.datetime.Instant +import ru.otus.otuskotlin.marketplace.common.NONE +import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates + data class MkplAd( var id: MkplAdId = MkplAdId.NONE, var title: String = "", @@ -9,7 +13,12 @@ data class MkplAd( var visibility: MkplVisibility = MkplVisibility.NONE, var productId: MkplProductId = MkplProductId.NONE, var lock: MkplAdLock = MkplAdLock.NONE, - val permissionsClient: MutableSet = mutableSetOf() + val permissionsClient: MutableSet = mutableSetOf(), + + var adState: SMAdStates = SMAdStates.NONE, + var views: Int = 0, + var timePublished: Instant = Instant.NONE, + var timeUpdated: Instant = Instant.NONE, ) { fun deepCopy(): MkplAd = copy( permissionsClient = permissionsClient.toMutableSet(), diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt new file mode 100644 index 0000000..dc0b662 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt @@ -0,0 +1,11 @@ +package ru.otus.otuskotlin.marketplace.common.statemachine + +interface ISMAdStateResolver { + fun resolve(signal: SMAdSignal): SMTransition + + companion object { + val NONE = object: ISMAdStateResolver { + override fun resolve(signal: SMAdSignal): SMTransition = SMTransition.ERROR + } + } +} diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt new file mode 100644 index 0000000..b2c9f48 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt @@ -0,0 +1,9 @@ +package ru.otus.otuskotlin.marketplace.common.statemachine + +import kotlin.time.Duration + +data class SMAdSignal( + val state: SMAdStates, + val duration: Duration, + val views: Int, +) diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdStates.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdStates.kt new file mode 100644 index 0000000..9f0caa1 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdStates.kt @@ -0,0 +1,16 @@ +package ru.otus.otuskotlin.marketplace.common.statemachine + + +@Suppress("unused") +enum class SMAdStates { + NONE, // не инициализировано состояние + DRAFT, // черновик + NEW, // только что опубликовано + ACTUAL, // актуальное + OLD, // старое объявление + HIT, // новое объявление с большим числом просмотров + POPULAR, // актуальное объявление с большим числом просмотров + + ERROR, // ошибки вычисления; + +} diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMTransition.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMTransition.kt new file mode 100644 index 0000000..a2429bc --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMTransition.kt @@ -0,0 +1,10 @@ +package ru.otus.otuskotlin.marketplace.common.statemachine + +data class SMTransition( + val state: SMAdStates, + val description: String, +) { + companion object { + val ERROR = SMTransition(SMAdStates.ERROR, "Unprovided transition occurred") + } +} diff --git a/ok-marketplace-be/settings.gradle.kts b/ok-marketplace-be/settings.gradle.kts index 1bf5c7c..6fbc432 100644 --- a/ok-marketplace-be/settings.gradle.kts +++ b/ok-marketplace-be/settings.gradle.kts @@ -35,6 +35,7 @@ include(":ok-marketplace-api-log1") include(":ok-marketplace-common") include(":ok-marketplace-biz") +include(":ok-marketplace-biz-state") include(":ok-marketplace-stubs") include(":ok-marketplace-app-common") From c48d1db4d3d634c3242064ead0dd7cabc04d2cc3 Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Thu, 23 May 2024 12:38:52 +0500 Subject: [PATCH 3/9] m8l1 Statemachine integration --- deploy/docker-compose.yml | 5 +- .../kotlin/statemachine/ComputeAdState.kt | 37 ------------- .../src/commonMain/kotlin/MkplCorSettings.kt | 2 - ok-marketplace-states/.gitignore | 8 +++ ok-marketplace-states/build.gradle.kts | 37 +++++++++++++ ok-marketplace-states/gradle.properties | 6 +++ .../build.gradle.kts | 2 +- .../kotlin/MkplGetStateProcessor.kt | 45 ++++++++++++++++ .../kotlin/MkplUpdateStateProcessor.kt | 54 +++++++++++++++++++ .../kotlin/general/ComputeAdState.kt | 39 ++++++++++++++ .../src/commonMain/kotlin/general/InitRepo.kt | 12 +++++ .../kotlin/general/PrepareResponse.kt | 12 +++++ .../kotlin/general/ReadStateFromDb.kt | 12 +++++ .../commonMain/kotlin/helper/InitStatus.kt | 15 ++++++ .../resolver}/SMAdStateResolverDefault.kt | 10 ++-- .../src/commonMain/kotlin/stubs/StubNoCase.kt | 26 +++++++++ .../src/commonMain/kotlin/stubs/Stubs.kt | 13 +++++ .../kotlin/validation/FinishValidation.kt | 14 +++++ .../kotlin/validation/ValidateIdNotEmpty.kt | 21 ++++++++ .../validation/ValidateIdProperFormat.kt | 28 ++++++++++ .../kotlin/validation/Validation.kt | 13 +++++ .../src/commonTest/kotlin/SMAdStateTest.kt | 5 +- .../build.gradle.kts | 26 +++++++++ .../src/commonMain/kotlin/Constants.kt | 7 +++ .../commonMain/kotlin/IMkplStateContext.kt | 15 ++++++ .../commonMain/kotlin/MkplAllStatesContext.kt | 20 +++++++ .../src/commonMain/kotlin/MkplStateContext.kt | 24 +++++++++ .../kotlin/MkplStatesCorSettings.kt | 14 +++++ .../kotlin/helpers/MkplErrorsHelpers.kt | 48 +++++++++++++++++ .../commonMain/kotlin/models/MkplAdStateId.kt | 12 +++++ .../src/commonMain/kotlin/models/MkplError.kt | 12 +++++ .../src/commonMain/kotlin/models/MkplState.kt | 8 +++ .../commonMain/kotlin/models/MkplStateRq.kt | 16 ++++++ .../src/commonMain/kotlin/models/MkplStubs.kt | 6 +++ .../commonMain/kotlin/models/MkplWorkMode.kt | 7 +++ .../kotlin/statemachine/ISMAdStateResolver.kt | 2 +- .../kotlin/statemachine/SMAdSignal.kt | 2 +- .../kotlin/statemachine/SMAdStates.kt | 2 +- .../kotlin/statemachine/SMTransition.kt | 2 +- ok-marketplace-states/settings.gradle.kts | 32 +++++++++++ settings.gradle.kts | 3 +- 41 files changed, 620 insertions(+), 54 deletions(-) delete mode 100644 ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt create mode 100644 ok-marketplace-states/.gitignore create mode 100644 ok-marketplace-states/build.gradle.kts create mode 100644 ok-marketplace-states/gradle.properties rename {ok-marketplace-be/ok-marketplace-biz-state => ok-marketplace-states/ok-marketplace-states-biz}/build.gradle.kts (92%) create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/InitRepo.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/PrepareResponse.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ReadStateFromDb.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/helper/InitStatus.kt rename {ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin => ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/resolver}/SMAdStateResolverDefault.kt (88%) create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubNoCase.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/Stubs.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/FinishValidation.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdNotEmpty.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdProperFormat.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/Validation.kt rename {ok-marketplace-be/ok-marketplace-biz-state => ok-marketplace-states/ok-marketplace-states-biz}/src/commonTest/kotlin/SMAdStateTest.kt (81%) create mode 100644 ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/Constants.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/IMkplStateContext.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplAllStatesContext.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStatesCorSettings.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/helpers/MkplErrorsHelpers.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplAdStateId.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplError.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplState.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStateRq.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStubs.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplWorkMode.kt rename {ok-marketplace-be/ok-marketplace-common => ok-marketplace-states/ok-marketplace-states-common}/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt (79%) rename {ok-marketplace-be/ok-marketplace-common => ok-marketplace-states/ok-marketplace-states-common}/src/commonMain/kotlin/statemachine/SMAdSignal.kt (66%) rename {ok-marketplace-be/ok-marketplace-common => ok-marketplace-states/ok-marketplace-states-common}/src/commonMain/kotlin/statemachine/SMAdStates.kt (89%) rename {ok-marketplace-be/ok-marketplace-common => ok-marketplace-states/ok-marketplace-states-common}/src/commonMain/kotlin/statemachine/SMTransition.kt (75%) create mode 100644 ok-marketplace-states/settings.gradle.kts diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 62030e3..9cccb14 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -58,6 +58,7 @@ services: - http.port=9200 - bootstrap.memory_lock=true - ES_JAVA_OPTS=-Xms512m -Xmx512m + - OPENSEARCH_INITIAL_ADMIN_PASSWORD=adm-Password0 ports: - '9200:9200' - '9600:9600' @@ -68,8 +69,8 @@ services: nofile: soft: 65536 hard: 65536 - volumes: - - opensearch-data:/usr/share/opensearch/data +# volumes: +# - opensearch-data:/usr/share/opensearch/data dashboards: image: opensearchproject/opensearch-dashboards:latest diff --git a/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt deleted file mode 100644 index dbcea06..0000000 --- a/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/statemachine/ComputeAdState.kt +++ /dev/null @@ -1,37 +0,0 @@ -package ru.otus.otuskotlin.marketplace.biz.statemachine - -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import ru.otus.otuskotlin.marketplace.common.MkplContext -import ru.otus.otuskotlin.marketplace.common.NONE -import ru.otus.otuskotlin.marketplace.common.models.MkplState -import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdSignal -import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates -import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl -import ru.otus.otuskotlin.marketplace.cor.worker -import kotlin.reflect.KClass - -private val clazz: KClass<*> = ICorChainDsl::computeAdState::class -fun ICorChainDsl.computeAdState(title: String) = worker { - this.title = title - this.description = "Вычисление состояния объявления" - on { state == MkplState.RUNNING } - handle { - val machine = this.corSettings.stateMachine - val log = corSettings.loggerProvider.logger(clazz) - val timeNow = Clock.System.now() - val ad = adValidated - val prevState = ad.adState - val timePublished = ad.timePublished.takeIf { it != Instant.NONE } ?: timeNow - val signal = SMAdSignal( - state = prevState.takeIf { it != SMAdStates.NONE } ?: SMAdStates.NEW, - duration = timeNow - timePublished, - views = ad.views, - ) - val transition = machine.resolve(signal) - if (transition.state != prevState) { - log.info("New ad state transition: ${transition.description}") - } - ad.adState = transition.state - } -} diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt index 5bb3c21..4ca406e 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt @@ -1,7 +1,6 @@ package ru.otus.otuskotlin.marketplace.common import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd -import ru.otus.otuskotlin.marketplace.common.statemachine.ISMAdStateResolver import ru.otus.otuskotlin.marketplace.common.ws.IMkplWsSessionRepo import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider @@ -11,7 +10,6 @@ data class MkplCorSettings( val repoStub: IRepoAd = IRepoAd.NONE, val repoTest: IRepoAd = IRepoAd.NONE, val repoProd: IRepoAd = IRepoAd.NONE, - val stateMachine: ISMAdStateResolver = ISMAdStateResolver.NONE ) { companion object { val NONE = MkplCorSettings() diff --git a/ok-marketplace-states/.gitignore b/ok-marketplace-states/.gitignore new file mode 100644 index 0000000..d635568 --- /dev/null +++ b/ok-marketplace-states/.gitignore @@ -0,0 +1,8 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +/.idea/ +/kotlin-js-store/ diff --git a/ok-marketplace-states/build.gradle.kts b/ok-marketplace-states/build.gradle.kts new file mode 100644 index 0000000..2ecdb60 --- /dev/null +++ b/ok-marketplace-states/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.kotlin.multiplatform) apply false + alias(libs.plugins.kotlin.kapt) apply false + alias(libs.plugins.muschko.remote) apply false + alias(libs.plugins.muschko.java) apply false +} + +group = "ru.otus.otuskotlin.marketplace" +version = "0.0.1" + +allprojects { + repositories { + mavenCentral() + } +} + +subprojects { + group = rootProject.group + version = rootProject.version +} + +ext { + val specDir = layout.projectDirectory.dir("../specs") + set("spec-v1", specDir.file("specs-ad-v1.yaml").toString()) + set("spec-v2", specDir.file("specs-ad-v2.yaml").toString()) + set("spec-log1", specDir.file("specs-ad-log1.yaml").toString()) +} + +tasks { + arrayOf("build", "clean", "check").forEach {tsk -> + create(tsk) { + group = "build" + dependsOn(subprojects.map { it.getTasksByName(tsk,false)}) + } + } +} diff --git a/ok-marketplace-states/gradle.properties b/ok-marketplace-states/gradle.properties new file mode 100644 index 0000000..541bd1f --- /dev/null +++ b/ok-marketplace-states/gradle.properties @@ -0,0 +1,6 @@ +kotlin.code.style=official +kotlin.native.ignoreDisabledTargets=true +kotlin.native.cacheKind.linuxX64=none +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 + + diff --git a/ok-marketplace-be/ok-marketplace-biz-state/build.gradle.kts b/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts similarity index 92% rename from ok-marketplace-be/ok-marketplace-biz-state/build.gradle.kts rename to ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts index 2606985..7c0dd46 100644 --- a/ok-marketplace-be/ok-marketplace-biz-state/build.gradle.kts +++ b/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts @@ -12,7 +12,7 @@ kotlin { implementation(libs.cor) - implementation(project(":ok-marketplace-common")) + implementation(projects.okMarketplaceStatesCommon) } } commonTest { diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt new file mode 100644 index 0000000..56e8884 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt @@ -0,0 +1,45 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine + +import ru.otus.otuskotlin.marketplace.biz.statemachine.general.initRepo +import ru.otus.otuskotlin.marketplace.biz.statemachine.general.prepareResponse +import ru.otus.otuskotlin.marketplace.biz.statemachine.general.readStateFromDb +import ru.otus.otuskotlin.marketplace.biz.statemachine.helper.initStatus +import ru.otus.otuskotlin.marketplace.biz.statemachine.helper.operation +import ru.otus.otuskotlin.marketplace.biz.statemachine.stubs.stubNoCase +import ru.otus.otuskotlin.marketplace.biz.statemachine.stubs.stubs +import ru.otus.otuskotlin.marketplace.biz.statemachine.validation.finishValidation +import ru.otus.otuskotlin.marketplace.biz.statemachine.validation.validateIdNotEmpty +import ru.otus.otuskotlin.marketplace.biz.statemachine.validation.validateIdProperFormat +import ru.otus.otuskotlin.marketplace.biz.statemachine.validation.validation +import ru.otus.otuskotlin.marketplace.cor.rootChain +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.MkplStatesCorSettings +import ru.otus.otuskotlin.marketplace.states.common.models.MkplAdStateId +import ru.otus.otuskotlin.marketplace.states.common.models.MkplStateCommand + +class MkplAdStateProcessor( + private val corSettings: MkplStatesCorSettings = MkplStatesCorSettings.NONE +) { + suspend fun exec(ctx: MkplStateContext) = businessChain.exec(ctx.also { it.corSettings = corSettings }) + + private val businessChain = rootChain { + initStatus("Инициализация статуса") + initRepo("Инициализация репозитория") + + stubs("Обработка стабов") { + stubNoCase("Ошибка: запрошенный стаб недопустим") + } + validation { + worker("Копируем поля в adValidating") { rqValidating = stateRequest.deepCopy() } + worker("Очистка id") { rqValidating.adId = MkplAdStateId(rqValidating.adId.asString().trim()) } + validateIdNotEmpty("Проверка на непустой id") + validateIdProperFormat("Проверка формата id") + + finishValidation("Успешное завершение процедуры валидации") + } + readStateFromDb("Чтение состояния из БД") + prepareResponse("Подготовка ответа") + }.build() +} + diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt new file mode 100644 index 0000000..40386be --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt @@ -0,0 +1,54 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine + +import ru.otus.otuskotlin.marketplace.biz.statemachine.general.computeAdState +import ru.otus.otuskotlin.marketplace.biz.statemachine.general.initRepo +import ru.otus.otuskotlin.marketplace.biz.statemachine.helper.initStatus +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.rootChain +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplAllStatesContext +import ru.otus.otuskotlin.marketplace.states.common.MkplStatesCorSettings + +class MkplUpdateStateProcessor( + private val corSettings: MkplStatesCorSettings = MkplStatesCorSettings.NONE +) { + suspend fun exec(ctx: MkplAllStatesContext) = businessChain.exec(ctx.also { it.corSettings = corSettings }) + + private val businessChain = rootChain { + initStatus("Инициализация статуса") + initRepo("Инициализация репозитория") + + readAllStatesWithLock("Чтение и блокировка всех объектов в БД на время вычисления") + requestStatisticsServer("Запрос сервера статистики") + computeAdState("Вычисление обновленного состояния") + updateStatesWithLock("Чтение и блокировка всех объектов в БД на время вычисления") + }.build() +} + +private fun ICorChainDsl.readAllStatesWithLock(title: String) { + this.title = title + this.description = """ + Запрашиваем все объекы из БД + Проверяем блокировку + - Если блокировка не установлена, ставим свою + - Если блокировка установлена давно, переписываем на свою + - Иначе пропускаем, она захвачена другим процессом + Прочитанные объекты направляем во flow + """.trimIndent() +} + +private fun ICorChainDsl.requestStatisticsServer(title: String) = worker { + this.title = title + this.description = """ + Выполняет батчевые запросы на сервер статистики (в мониторинг, OpenSearch) + """.trimIndent() +} + +private fun ICorChainDsl.updateStatesWithLock(title: String) = worker { + this.title = title + this.description = """ + Читает поток вычисленных обновлений и сохраняет их в БД с учетом блокировки. + Там, где блокировки изменилась, такие объекты пропускаем, их перехватил другой процесс + """.trimIndent() +} + diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt new file mode 100644 index 0000000..a4bddd3 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt @@ -0,0 +1,39 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.general + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplAllStatesContext +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.NONE +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState +import ru.otus.otuskotlin.marketplace.states.common.models.MkplStateRq +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdSignal +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdStates +import kotlin.reflect.KClass + +private val clazz: KClass<*> = ICorChainDsl::computeAdState::class +fun ICorChainDsl.computeAdState(title: String) = worker { + this.title = title + this.description = "Вычисление состояния объявления" + on { state == MkplState.RUNNING } + handle { + statesComputed = statesWStat.onEach { sc: MkplStateRq -> + val machine = this.corSettings.stateMachine + val log = corSettings.loggerProvider.logger(clazz) + val timeNow = Clock.System.now() + val timePublished = sc.created.takeIf { it != Instant.NONE } ?: timeNow + val signal = SMAdSignal( + state = sc.oldState.takeIf { it != SMAdStates.NONE } ?: SMAdStates.NEW, + duration = timeNow - timePublished, + views = sc.views, + ) + val transition = machine.resolve(signal) + if (transition.state != sc.oldState) { + log.info("New ad state transition: ${transition.description}") + } + sc.transition = transition + } + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/InitRepo.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/InitRepo.kt new file mode 100644 index 0000000..8243f67 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/InitRepo.kt @@ -0,0 +1,12 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.general + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.IMkplStateContext + +fun ICorChainDsl.initRepo(title: String) = worker { + this.title = title + this.description = """ + Вычисление актуального репозитория в зависимости от типа запроса + """.trimIndent() +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/PrepareResponse.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/PrepareResponse.kt new file mode 100644 index 0000000..08848a3 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/PrepareResponse.kt @@ -0,0 +1,12 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.general + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext + +fun ICorChainDsl.prepareResponse(title: String) = worker { + this.title = title + this.description = """ + Подготовка ответа + """.trimIndent() +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ReadStateFromDb.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ReadStateFromDb.kt new file mode 100644 index 0000000..642ce41 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ReadStateFromDb.kt @@ -0,0 +1,12 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.general + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext + +fun ICorChainDsl.readStateFromDb(title: String) = worker { + this.title = title + this.description = """ + Чтение состояния из БД + """.trimIndent() +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/helper/InitStatus.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/helper/InitStatus.kt new file mode 100644 index 0000000..ca9bd8a --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/helper/InitStatus.kt @@ -0,0 +1,15 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.helper + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.IMkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState + +fun ICorChainDsl.initStatus(title: String) = worker() { + this.title = title + this.description = """ + Этот обработчик устанавливает стартовый статус обработки. Запускается только в случае не заданного статуса. + """.trimIndent() + on { state == MkplState.NONE } + handle { state = MkplState.RUNNING } +} diff --git a/ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin/SMAdStateResolverDefault.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/resolver/SMAdStateResolverDefault.kt similarity index 88% rename from ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin/SMAdStateResolverDefault.kt rename to ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/resolver/SMAdStateResolverDefault.kt index f135a55..d0aff1e 100644 --- a/ok-marketplace-be/ok-marketplace-biz-state/src/commonMain/kotlin/SMAdStateResolverDefault.kt +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/resolver/SMAdStateResolverDefault.kt @@ -1,9 +1,9 @@ -package ru.otus.otuskotlin.marketplace.biz.statemachine +package ru.otus.otuskotlin.marketplace.biz.statemachine.resolver -import ru.otus.otuskotlin.marketplace.common.statemachine.ISMAdStateResolver -import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdSignal -import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates -import ru.otus.otuskotlin.marketplace.common.statemachine.SMTransition +import ru.otus.otuskotlin.marketplace.states.common.statemachine.ISMAdStateResolver +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdSignal +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdStates +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMTransition import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.milliseconds diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubNoCase.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubNoCase.kt new file mode 100644 index 0000000..19114fc --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubNoCase.kt @@ -0,0 +1,26 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.stubs + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.helpers.fail +import ru.otus.otuskotlin.marketplace.states.common.models.MkplError +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState + +fun ICorChainDsl.stubNoCase(title: String) = worker { + this.title = title + this.description = """ + Валидируем ситуацию, когда запрошен кейс, который не поддерживается в стабах + """.trimIndent() + on { state == MkplState.RUNNING } + handle { + fail( + MkplError( + code = "validation", + field = "stub", + group = "validation", + message = "Wrong stub case is requested: ${stubCase.name}" + ) + ) + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/Stubs.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/Stubs.kt new file mode 100644 index 0000000..d268a09 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/Stubs.kt @@ -0,0 +1,13 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.stubs + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.chain +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState +import ru.otus.otuskotlin.marketplace.states.common.models.MkplWorkMode + +fun ICorChainDsl.stubs(title: String, block: ICorChainDsl.() -> Unit) = chain { + block() + this.title = title + on { workMode == MkplWorkMode.STUB && state == MkplState.RUNNING } +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/FinishValidation.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/FinishValidation.kt new file mode 100644 index 0000000..d163693 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/FinishValidation.kt @@ -0,0 +1,14 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.validation + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState + +fun ICorChainDsl.finishValidation(title: String) = worker { + this.title = title + on { state == MkplState.RUNNING } + handle { + rqValidated = rqValidating + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdNotEmpty.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdNotEmpty.kt new file mode 100644 index 0000000..cfd46fa --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdNotEmpty.kt @@ -0,0 +1,21 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.validation + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.helpers.errorValidation +import ru.otus.otuskotlin.marketplace.states.common.helpers.fail + +fun ICorChainDsl.validateIdNotEmpty(title: String) = worker { + this.title = title + on { rqValidating.adId.asString().isEmpty() } + handle { + fail( + errorValidation( + field = "id", + violationCode = "empty", + description = "field must not be empty" + ) + ) + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdProperFormat.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdProperFormat.kt new file mode 100644 index 0000000..eade781 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/ValidateIdProperFormat.kt @@ -0,0 +1,28 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.validation + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.helpers.errorValidation +import ru.otus.otuskotlin.marketplace.states.common.helpers.fail +import ru.otus.otuskotlin.marketplace.states.common.models.MkplAdStateId + +fun ICorChainDsl.validateIdProperFormat(title: String) = worker { + this.title = title + + // Может быть вынесен в MkplAdId для реализации различных форматов + val regExp = Regex("^[0-9a-zA-Z#:-]+$") + on { rqValidating.adId != MkplAdStateId.NONE && !rqValidating.adId.asString().matches(regExp) } + handle { + val encodedId = rqValidating.adId.asString() + .replace("<", "<") + .replace(">", ">") + fail( + errorValidation( + field = "id", + violationCode = "badFormat", + description = "value $encodedId must contain only letters and numbers" + ) + ) + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/Validation.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/Validation.kt new file mode 100644 index 0000000..e66ee90 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/validation/Validation.kt @@ -0,0 +1,13 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.validation + +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.chain +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState + +fun ICorChainDsl.validation(block: ICorChainDsl.() -> Unit) = chain { + block() + title = "Валидация" + + on { state == MkplState.RUNNING } +} diff --git a/ok-marketplace-be/ok-marketplace-biz-state/src/commonTest/kotlin/SMAdStateTest.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonTest/kotlin/SMAdStateTest.kt similarity index 81% rename from ok-marketplace-be/ok-marketplace-biz-state/src/commonTest/kotlin/SMAdStateTest.kt rename to ok-marketplace-states/ok-marketplace-states-biz/src/commonTest/kotlin/SMAdStateTest.kt index 1cedf4c..9d37dc7 100644 --- a/ok-marketplace-be/ok-marketplace-biz-state/src/commonTest/kotlin/SMAdStateTest.kt +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonTest/kotlin/SMAdStateTest.kt @@ -1,7 +1,8 @@ package ru.otus.otuskotlin.marketplace.biz.statemachine -import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdSignal -import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates +import ru.otus.otuskotlin.marketplace.biz.statemachine.resolver.SMAdStateResolverDefault +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdSignal +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdStates import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals diff --git a/ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts b/ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts new file mode 100644 index 0000000..9b8d8d1 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("build-kmp") +} + +group = rootProject.group +version = rootProject.version + +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + implementation(kotlin("stdlib-common")) + + api(libs.kotlinx.datetime) + implementation(libs.coroutines.core) + api("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/Constants.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/Constants.kt new file mode 100644 index 0000000..b96b42b --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/Constants.kt @@ -0,0 +1,7 @@ +package ru.otus.otuskotlin.marketplace.states.common + +import kotlinx.datetime.Instant + +private val INSTANT_NONE = Instant.fromEpochMilliseconds(Long.MIN_VALUE) +val Instant.Companion.NONE + get() = INSTANT_NONE diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/IMkplStateContext.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/IMkplStateContext.kt new file mode 100644 index 0000000..2e9ab14 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/IMkplStateContext.kt @@ -0,0 +1,15 @@ +package ru.otus.otuskotlin.marketplace.states.common + +import kotlinx.datetime.Instant +import ru.otus.otuskotlin.marketplace.states.common.models.MkplError +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState + +interface IMkplStateContext { + var state: MkplState + val errors: MutableList + + var corSettings: MkplStatesCorSettings + + var timeStart: Instant + +} diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplAllStatesContext.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplAllStatesContext.kt new file mode 100644 index 0000000..9711911 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplAllStatesContext.kt @@ -0,0 +1,20 @@ +package ru.otus.otuskotlin.marketplace.states.common + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.datetime.Instant +import ru.otus.otuskotlin.marketplace.states.common.models.* + +data class MkplAllStatesContext( + override var state: MkplState = MkplState.NONE, + override val errors: MutableList = mutableListOf(), + + override var corSettings: MkplStatesCorSettings = MkplStatesCorSettings(), + + override var timeStart: Instant = Instant.NONE, + + var statesRead: Flow = flowOf(), + var statesWStat: Flow = flowOf(), + var statesComputed: Flow = flowOf(), + var statesUpdating: Flow = flowOf(), +): IMkplStateContext diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt new file mode 100644 index 0000000..9fb3534 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt @@ -0,0 +1,24 @@ +package ru.otus.otuskotlin.marketplace.states.common + +import kotlinx.datetime.Instant +import ru.otus.otuskotlin.marketplace.states.common.models.* + +data class MkplStateContext( + override var state: MkplState = MkplState.NONE, + var workMode: MkplWorkMode = MkplWorkMode.PROD, + var stubCase: MkplStubs = MkplStubs.NONE, + override val errors: MutableList = mutableListOf(), + + override var corSettings: MkplStatesCorSettings = MkplStatesCorSettings(), + + override var timeStart: Instant = Instant.NONE, + + var stateRequest: MkplStateRq = MkplStateRq(), + var rqValidating: MkplStateRq = MkplStateRq(), + var rqValidated: MkplStateRq = MkplStateRq(), + + var stateRead: MkplAdStateObj = MkplAdStateObj(), + var stateWStats: MkplAdStateObj = MkplAdStateObj(), + + var stateResponse: MkplStateRq = MkplStateRq(), +): IMkplStateContext diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStatesCorSettings.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStatesCorSettings.kt new file mode 100644 index 0000000..734b866 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStatesCorSettings.kt @@ -0,0 +1,14 @@ +package ru.otus.otuskotlin.marketplace.states.common + +import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider +import ru.otus.otuskotlin.marketplace.states.common.statemachine.ISMAdStateResolver + +data class MkplStatesCorSettings( + val loggerProvider: MpLoggerProvider = MpLoggerProvider(), + val stateMachine: ISMAdStateResolver = ISMAdStateResolver.NONE +) { + companion object { + val NONE = MkplStatesCorSettings() + } +} + diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/helpers/MkplErrorsHelpers.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/helpers/MkplErrorsHelpers.kt new file mode 100644 index 0000000..1e8d7b6 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/helpers/MkplErrorsHelpers.kt @@ -0,0 +1,48 @@ +package ru.otus.otuskotlin.marketplace.states.common.helpers + +import ru.otus.otuskotlin.marketplace.logging.common.LogLevel +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.models.MkplError +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState + +inline fun MkplStateContext.addError(error: MkplError) = errors.add(error) +inline fun MkplStateContext.addErrors(error: Collection) = errors.addAll(error) + +inline fun MkplStateContext.fail(error: MkplError) { + addError(error) + state = MkplState.FAILING +} + +inline fun MkplStateContext.fail(errors: Collection) { + addErrors(errors) + state = MkplState.FAILING +} + +inline fun errorValidation( + field: String, + /** + * Код, характеризующий ошибку. Не должен включать имя поля или указание на валидацию. + * Например: empty, badSymbols, tooLong, etc + */ + violationCode: String, + description: String, + level: LogLevel = LogLevel.ERROR, +) = MkplError( + code = "validation-$field-$violationCode", + field = field, + group = "validation", + message = "Validation error for field $field: $description", + level = level, +) + +inline fun errorSystem( + violationCode: String, + level: LogLevel = LogLevel.ERROR, + e: Throwable, +) = MkplError( + code = "system-$violationCode", + group = "system", + message = "System error occurred. Our stuff has been informed, please retry later", + level = level, + exception = e, +) diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplAdStateId.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplAdStateId.kt new file mode 100644 index 0000000..40c222e --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplAdStateId.kt @@ -0,0 +1,12 @@ +package ru.otus.otuskotlin.marketplace.states.common.models + +import kotlin.jvm.JvmInline + +@JvmInline +value class MkplAdStateId(private val id: String) { + fun asString() = id + + companion object { + val NONE = MkplAdStateId("") + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplError.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplError.kt new file mode 100644 index 0000000..9655a47 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplError.kt @@ -0,0 +1,12 @@ +package ru.otus.otuskotlin.marketplace.states.common.models + +import ru.otus.otuskotlin.marketplace.logging.common.LogLevel + +data class MkplError( + val code: String = "", + val group: String = "", + val field: String = "", + val message: String = "", + val level: LogLevel = LogLevel.ERROR, + val exception: Throwable? = null, +) diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplState.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplState.kt new file mode 100644 index 0000000..3c9b445 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplState.kt @@ -0,0 +1,8 @@ +package ru.otus.otuskotlin.marketplace.states.common.models + +enum class MkplState { + NONE, + RUNNING, + FAILING, + FINISHING, +} diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStateRq.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStateRq.kt new file mode 100644 index 0000000..1a17421 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStateRq.kt @@ -0,0 +1,16 @@ +package ru.otus.otuskotlin.marketplace.states.common.models + +import kotlinx.datetime.Instant +import ru.otus.otuskotlin.marketplace.states.common.NONE +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdStates +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMTransition + +data class MkplStateRq( + var adId: MkplAdStateId = MkplAdStateId.NONE, + var oldState: SMAdStates = SMAdStates.NONE, + var created: Instant = Instant.NONE, + var views: Int = 0, + var transition: SMTransition = SMTransition.ERROR, +) { + fun deepCopy() = copy() +} diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStubs.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStubs.kt new file mode 100644 index 0000000..e5e3631 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplStubs.kt @@ -0,0 +1,6 @@ +package ru.otus.otuskotlin.marketplace.states.common.models + +enum class MkplStubs { + NONE, + SUCCESS, +} diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplWorkMode.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplWorkMode.kt new file mode 100644 index 0000000..d20a61b --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/models/MkplWorkMode.kt @@ -0,0 +1,7 @@ +package ru.otus.otuskotlin.marketplace.states.common.models + +enum class MkplWorkMode { + PROD, + TEST, + STUB, +} diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt similarity index 79% rename from ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt rename to ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt index dc0b662..e9a4a68 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/ISMAdStateResolver.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.common.statemachine +package ru.otus.otuskotlin.marketplace.states.common.statemachine interface ISMAdStateResolver { fun resolve(signal: SMAdSignal): SMTransition diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt similarity index 66% rename from ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt rename to ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt index b2c9f48..0e20fb5 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMAdSignal.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.common.statemachine +package ru.otus.otuskotlin.marketplace.states.common.statemachine import kotlin.time.Duration diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdStates.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMAdStates.kt similarity index 89% rename from ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdStates.kt rename to ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMAdStates.kt index 9f0caa1..ce14968 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMAdStates.kt +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMAdStates.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.common.statemachine +package ru.otus.otuskotlin.marketplace.states.common.statemachine @Suppress("unused") diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMTransition.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMTransition.kt similarity index 75% rename from ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMTransition.kt rename to ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMTransition.kt index a2429bc..4f0c00b 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/statemachine/SMTransition.kt +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMTransition.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.common.statemachine +package ru.otus.otuskotlin.marketplace.states.common.statemachine data class SMTransition( val state: SMAdStates, diff --git a/ok-marketplace-states/settings.gradle.kts b/ok-marketplace-states/settings.gradle.kts new file mode 100644 index 0000000..0a2e4f8 --- /dev/null +++ b/ok-marketplace-states/settings.gradle.kts @@ -0,0 +1,32 @@ +rootProject.name = "ok-marketplace-states" + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +pluginManagement { + includeBuild("../build-plugin") + plugins { + id("build-jvm") apply false + id("build-kmp") apply false + } + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} + +// Включает вот такую конструкцию +//implementation(projects.m2l5Gradle.sub1.ssub1) +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +include(":ok-marketplace-states-common") +include(":ok-marketplace-states-biz") diff --git a/settings.gradle.kts b/settings.gradle.kts index a830cea..c35865e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,7 +12,8 @@ rootProject.name = "ok-marketplace-202312" //includeBuild("lessons") includeBuild("ok-marketplace-be") +includeBuild("ok-marketplace-states") includeBuild("ok-marketplace-libs") includeBuild("ok-marketplace-tests") -includeBuild("pgkn") \ No newline at end of file +includeBuild("pgkn") From 1f3cca62082d3641af29f0cecfb66fcfd4367248 Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Thu, 23 May 2024 20:22:48 +0500 Subject: [PATCH 4/9] m8l1 Statemachine integration: Fix --- .../ok-marketplace-states-biz/build.gradle.kts | 1 + .../src/commonMain/kotlin/general/ComputeAdState.kt | 2 +- .../src/commonMain/kotlin/MkplStateContext.kt | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts b/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts index 7c0dd46..3750eb4 100644 --- a/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts +++ b/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts @@ -11,6 +11,7 @@ kotlin { implementation(kotlin("stdlib-common")) implementation(libs.cor) + implementation(libs.coroutines.core) implementation(projects.okMarketplaceStatesCommon) } diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt index a4bddd3..9f389bb 100644 --- a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt @@ -1,11 +1,11 @@ package ru.otus.otuskotlin.marketplace.biz.statemachine.general +import kotlinx.coroutines.flow.onEach import kotlinx.datetime.Clock import kotlinx.datetime.Instant import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl import ru.otus.otuskotlin.marketplace.cor.worker import ru.otus.otuskotlin.marketplace.states.common.MkplAllStatesContext -import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext import ru.otus.otuskotlin.marketplace.states.common.NONE import ru.otus.otuskotlin.marketplace.states.common.models.MkplState import ru.otus.otuskotlin.marketplace.states.common.models.MkplStateRq diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt index 9fb3534..24f065c 100644 --- a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/MkplStateContext.kt @@ -17,8 +17,8 @@ data class MkplStateContext( var rqValidating: MkplStateRq = MkplStateRq(), var rqValidated: MkplStateRq = MkplStateRq(), - var stateRead: MkplAdStateObj = MkplAdStateObj(), - var stateWStats: MkplAdStateObj = MkplAdStateObj(), + var stateRead: MkplStateRq = MkplStateRq(), + var stateWStats: MkplStateRq = MkplStateRq(), var stateResponse: MkplStateRq = MkplStateRq(), ): IMkplStateContext From e1be6a67710525085184e372316706d13c96dafc Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Thu, 23 May 2024 23:41:01 +0500 Subject: [PATCH 5/9] m8l1 Statemachine integration: Fix 1 --- .../src/commonMain/kotlin/MkplGetStateProcessor.kt | 2 -- .../src/commonMain/kotlin/MkplUpdateStateProcessor.kt | 3 ++- .../src/commonMain/kotlin/general/ComputeAdState.kt | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt index 56e8884..e8fa9e6 100644 --- a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt @@ -4,7 +4,6 @@ import ru.otus.otuskotlin.marketplace.biz.statemachine.general.initRepo import ru.otus.otuskotlin.marketplace.biz.statemachine.general.prepareResponse import ru.otus.otuskotlin.marketplace.biz.statemachine.general.readStateFromDb import ru.otus.otuskotlin.marketplace.biz.statemachine.helper.initStatus -import ru.otus.otuskotlin.marketplace.biz.statemachine.helper.operation import ru.otus.otuskotlin.marketplace.biz.statemachine.stubs.stubNoCase import ru.otus.otuskotlin.marketplace.biz.statemachine.stubs.stubs import ru.otus.otuskotlin.marketplace.biz.statemachine.validation.finishValidation @@ -16,7 +15,6 @@ import ru.otus.otuskotlin.marketplace.cor.worker import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext import ru.otus.otuskotlin.marketplace.states.common.MkplStatesCorSettings import ru.otus.otuskotlin.marketplace.states.common.models.MkplAdStateId -import ru.otus.otuskotlin.marketplace.states.common.models.MkplStateCommand class MkplAdStateProcessor( private val corSettings: MkplStatesCorSettings = MkplStatesCorSettings.NONE diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt index 40386be..a0de917 100644 --- a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplUpdateStateProcessor.kt @@ -21,6 +21,7 @@ class MkplUpdateStateProcessor( readAllStatesWithLock("Чтение и блокировка всех объектов в БД на время вычисления") requestStatisticsServer("Запрос сервера статистики") computeAdState("Вычисление обновленного состояния") +// filterUpdated("Вычисляет объявления для сохранения") updateStatesWithLock("Чтение и блокировка всех объектов в БД на время вычисления") }.build() } @@ -29,7 +30,7 @@ private fun ICorChainDsl.readAllStatesWithLock(title: Stri this.title = title this.description = """ Запрашиваем все объекы из БД - Проверяем блокировку + Проверяем блокировку и время последнего обновления - Если блокировка не установлена, ставим свою - Если блокировка установлена давно, переписываем на свою - Иначе пропускаем, она захвачена другим процессом diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt index 9f389bb..d871cca 100644 --- a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/general/ComputeAdState.kt @@ -19,9 +19,9 @@ fun ICorChainDsl.computeAdState(title: String) = worker { this.description = "Вычисление состояния объявления" on { state == MkplState.RUNNING } handle { + val machine = this.corSettings.stateMachine + val log = corSettings.loggerProvider.logger(clazz) statesComputed = statesWStat.onEach { sc: MkplStateRq -> - val machine = this.corSettings.stateMachine - val log = corSettings.loggerProvider.logger(clazz) val timeNow = Clock.System.now() val timePublished = sc.created.takeIf { it != Instant.NONE } ?: timeNow val signal = SMAdSignal( From 563754e8f7002a661ad9af2d1deafecbebf78df4 Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Sun, 26 May 2024 00:45:22 +0500 Subject: [PATCH 6/9] m8l1 Statemachine integration: Fix 1 --- .../src/main/kotlin/BuildPluginJvm.kt | 24 ++++++++++ .../main/kotlin/BuildPluginMultiplatform.kt | 3 +- build.gradle.kts | 7 ++- gradle/libs.versions.toml | 16 +++++-- .../ok-marketplace-app-kafka/build.gradle.kts | 2 +- .../ok-marketplace-app-ktor/build.gradle.kts | 19 ++++---- .../kotlin/plugins/InitAppSettings.kt | 11 +++-- .../build.gradle.kts | 2 +- .../build.gradle.kts | 6 ++- .../src/main/kotlin/config/AdConfig.kt | 2 - .../ok-marketplace-app-tmp/build.gradle.kts | 2 +- .../ok-marketplace-biz/build.gradle.kts | 3 +- .../src/commonMain/kotlin/MkplAdProcessor.kt | 9 +++- .../commonMain/kotlin/general/GetAdState.kt | 17 +++++++ .../commonMain/kotlin/general/GetAdStates.kt | 17 +++++++ .../ok-marketplace-common/build.gradle.kts | 2 +- .../src/commonMain/kotlin/MkplContext.kt | 4 ++ .../src/commonMain/kotlin/MkplCorSettings.kt | 2 + .../src/commonMain/kotlin/models/MkplAd.kt | 7 +-- ok-marketplace-be/settings.gradle.kts | 1 - ok-marketplace-states/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../kotlin/MkplGetStateProcessor.kt | 3 +- .../commonMain/kotlin/stubs/StubSuccess.kt | 31 ++++++++++++ .../src/commonTest/kotlin/SMAdStateBizTest.kt | 48 +++++++++++++++++++ .../build.gradle.kts | 2 +- .../kotlin/statemachine/SMTransition.kt | 1 + ok-marketplace-tests/settings.gradle.kts | 2 - 28 files changed, 208 insertions(+), 39 deletions(-) create mode 100644 ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdState.kt create mode 100644 ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdStates.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubSuccess.kt create mode 100644 ok-marketplace-states/ok-marketplace-states-biz/src/commonTest/kotlin/SMAdStateBizTest.kt diff --git a/build-plugin/src/main/kotlin/BuildPluginJvm.kt b/build-plugin/src/main/kotlin/BuildPluginJvm.kt index 50335ac..9e9f5f9 100644 --- a/build-plugin/src/main/kotlin/BuildPluginJvm.kt +++ b/build-plugin/src/main/kotlin/BuildPluginJvm.kt @@ -1,13 +1,37 @@ package ru.otus.otuskotlin.marketplace.plugin +import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.the +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension @Suppress("unused") internal class BuildPluginJvm : Plugin { override fun apply(project: Project) = with(project) { pluginManager.apply("org.jetbrains.kotlin.jvm") + plugins.withId("org.jetbrains.kotlin.jvm") { + extensions.configure { + val libs = project.the() + sourceSets.configureEach { + languageSettings.apply { + languageVersion = libs.versions.kotlin.language.get() + progressiveMode = true + optIn("kotlin.time.ExperimentalTime") + } + } + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvm.language.get())) + } + compilerOptions { + jvmTarget.set(JvmTarget.valueOf("JVM_${libs.versions.jvm.compiler.get()}")) + } + } + } group = rootProject.group version = rootProject.version } diff --git a/build-plugin/src/main/kotlin/BuildPluginMultiplatform.kt b/build-plugin/src/main/kotlin/BuildPluginMultiplatform.kt index 56a869a..e2dd6da 100644 --- a/build-plugin/src/main/kotlin/BuildPluginMultiplatform.kt +++ b/build-plugin/src/main/kotlin/BuildPluginMultiplatform.kt @@ -20,10 +20,11 @@ internal class BuildPluginMultiplatform : Plugin { plugins.withId("org.jetbrains.kotlin.multiplatform") { extensions.configure { + val libs = project.the() configureTargets(this@with) sourceSets.configureEach { languageSettings.apply { - languageVersion = "1.9" + languageVersion = libs.versions.kotlin.language.get() progressiveMode = true optIn("kotlin.time.ExperimentalTime") } diff --git a/build.gradle.kts b/build.gradle.kts index f869aae..aa067a1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,12 @@ subprojects { } tasks { + create("clean") { + group = "build" + gradle.includedBuilds.forEach { + dependsOn(it.task(":clean")) + } + } val buildImages: Task by creating { dependsOn(gradle.includedBuild("ok-marketplace-be").task(":buildImages")) } @@ -29,7 +35,6 @@ tasks { create("check") { group = "verification" -// dependsOn(gradle.includedBuild("ok-marketplace-be").task(":check")) dependsOn(buildImages) dependsOn(e2eTests) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca51b45..22424d6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +mkpl = "0.0.1" kotlin = "1.9.23" kotlinx-datetime = "0.5.0" @@ -29,8 +30,9 @@ testcontainers = "1.19.7" muschko = "9.4.0" # BASE -jvm-compiler = "17" -jvm-language = "17" +kotlin-language = "1.9" +jvm-compiler = "21" +jvm-language = "21" [libraries] plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } @@ -44,7 +46,6 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve coroutines-reactor = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor", version.ref = "coroutines" } coroutines-reactive = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactive", version.ref = "coroutines" } coroutines-jdk9 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9", version.ref = "coroutines" } -cor = { module = "ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-cor" } uuid = "com.benasher44:uuid:0.8.4" jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } @@ -123,6 +124,15 @@ testcontainers-rabbitmq = { module = "org.testcontainers:rabbitmq", version.ref testcontainers-postgres = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" } testcontainers-cassandra = { module = "org.testcontainers:cassandra", version.ref = "testcontainers" } +mkpl-logs-common = { module = "ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common", version.ref = "mkpl" } +mkpl-logs-kermit = { module = "ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-kermit", version.ref = "mkpl" } +mkpl-logs-logback = { module = "ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-logback", version.ref = "mkpl" } +mkpl-logs-socket = { module = "ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-socket", version.ref = "mkpl" } +mkpl-cor = { module = "ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-cor", version.ref = "mkpl" } +mkpl-state-common = { module = "ru.otus.otuskotlin.marketplace.state:ok-marketplace-states-common", version.ref = "mkpl" } +mkpl-state-biz = { module = "ru.otus.otuskotlin.marketplace.state:ok-marketplace-states-biz", version.ref = "mkpl" } + + [bundles] kotest = ["kotest-junit5", "kotest-core", "kotest-datatest", "kotest-property"] exposed = ["db-exposed-core", "db-exposed-dao", "db-exposed-jdbc"] diff --git a/ok-marketplace-be/ok-marketplace-app-kafka/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-kafka/build.gradle.kts index 53e03ee..f887593 100644 --- a/ok-marketplace-be/ok-marketplace-app-kafka/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-kafka/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { implementation(libs.coroutines.core) implementation(libs.kotlinx.atomicfu) - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-logback") + implementation(libs.mkpl.logs.logback) implementation(project(":ok-marketplace-app-common")) diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts index ceacff6..ae1e36d 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts @@ -22,7 +22,7 @@ ktor { docker { localImageName.set(project.name) imageTag.set(project.version.toString()) - jreVersion.set(JavaVersion.VERSION_17) + jreVersion.set(JavaVersion.valueOf("VERSION_${libs.versions.jvm.compiler.get()}")) } } @@ -81,13 +81,15 @@ kotlin { implementation(projects.okMarketplaceRepoInmemory) implementation(projects.okMarketplaceRepoPostgres) - implementation(projects.okMarketplaceBizState) + // States + implementation(libs.mkpl.state.common) + implementation(libs.mkpl.state.biz) // logging implementation(project(":ok-marketplace-api-log1")) - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common") - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-kermit") - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-socket") + implementation(libs.mkpl.logs.common) + implementation(libs.mkpl.logs.kermit) + implementation(libs.mkpl.logs.socket) } } @@ -117,13 +119,14 @@ kotlin { implementation(libs.logback) // transport models - implementation(project(":ok-marketplace-api-v1-jackson")) - implementation(project(":ok-marketplace-api-v1-mappers")) + implementation(projects.okMarketplaceApiV1Jackson) + implementation(projects.okMarketplaceApiV1Mappers) + implementation(projects.okMarketplaceApiV2Kmp) implementation(projects.okMarketplaceRepoCassandra) implementation(projects.okMarketplaceRepoGremlin) - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-logback") + implementation(libs.mkpl.logs.logback) implementation(libs.testcontainers.cassandra) implementation(libs.testcontainers.core) } diff --git a/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt b/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt index 4df8f0e..998986a 100644 --- a/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt +++ b/ok-marketplace-be/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt @@ -5,17 +5,22 @@ import ru.otus.otuskotlin.marketplace.app.ktor.MkplAppSettings import ru.otus.otuskotlin.marketplace.app.ktor.base.KtorWsSessionRepo import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor -import ru.otus.otuskotlin.marketplace.biz.statemachine.SMAdStateResolverDefault +import ru.otus.otuskotlin.marketplace.biz.statemachine.resolver.SMAdStateResolverDefault import ru.otus.otuskotlin.marketplace.common.MkplCorSettings +import ru.otus.otuskotlin.marketplace.states.common.MkplStatesCorSettings fun Application.initAppSettings(): MkplAppSettings { + val loggerProvider = getLoggerProviderConf() val corSettings = MkplCorSettings( - loggerProvider = getLoggerProviderConf(), + loggerProvider = loggerProvider, wsSessions = KtorWsSessionRepo(), repoTest = getDatabaseConf(AdDbType.TEST), repoProd = getDatabaseConf(AdDbType.PROD), repoStub = AdRepoStub(), - stateMachine = SMAdStateResolverDefault(), + stateSettings = MkplStatesCorSettings( + loggerProvider = loggerProvider, + stateMachine = SMAdStateResolverDefault(), + ), ) return MkplAppSettings( appUrls = environment.config.propertyOrNull("ktor.urls")?.getList() ?: emptyList(), diff --git a/ok-marketplace-be/ok-marketplace-app-rabbit/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-rabbit/build.gradle.kts index deb66c9..3fd4f7a 100644 --- a/ok-marketplace-be/ok-marketplace-app-rabbit/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-rabbit/build.gradle.kts @@ -20,7 +20,7 @@ dependencies { implementation(project(":ok-marketplace-common")) implementation(project(":ok-marketplace-app-common")) - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-logback") + implementation(libs.mkpl.logs.logback) // v1 api implementation(project(":ok-marketplace-api-v1-jackson")) diff --git a/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts index 9f4a25f..7139d5f 100644 --- a/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-spring/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { // Внутренние модели implementation(project(":ok-marketplace-common")) implementation(project(":ok-marketplace-app-common")) - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-logback") + implementation(libs.mkpl.logs.logback) // v1 api implementation(project(":ok-marketplace-api-v1-jackson")) @@ -42,7 +42,9 @@ dependencies { testImplementation(projects.okMarketplaceRepoCommon) testImplementation(projects.okMarketplaceStubs) - implementation(projects.okMarketplaceBizState) + // State + implementation(libs.mkpl.state.common) + implementation(libs.mkpl.state.biz) // tests testImplementation(kotlin("test-junit5")) diff --git a/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt b/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt index d819895..3e18e56 100644 --- a/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt +++ b/ok-marketplace-be/ok-marketplace-app-spring/src/main/kotlin/config/AdConfig.kt @@ -10,7 +10,6 @@ import ru.otus.otuskotlin.markeplace.app.spring.base.SpringWsSessionRepo import ru.otus.otuskotlin.marketplace.backend.repo.postgresql.RepoAdSql import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor -import ru.otus.otuskotlin.marketplace.biz.statemachine.SMAdStateResolverDefault import ru.otus.otuskotlin.marketplace.common.MkplCorSettings import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider @@ -48,7 +47,6 @@ class AdConfig(val postgresConfig: AdConfigPostgres) { repoTest = testRepo(), repoProd = prodRepo(), repoStub = stubRepo(), - stateMachine = SMAdStateResolverDefault() ) @Bean diff --git a/ok-marketplace-be/ok-marketplace-app-tmp/build.gradle.kts b/ok-marketplace-be/ok-marketplace-app-tmp/build.gradle.kts index 8b9ff1f..d024f43 100644 --- a/ok-marketplace-be/ok-marketplace-app-tmp/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-app-tmp/build.gradle.kts @@ -10,7 +10,7 @@ application { dependencies { implementation(project(":ok-marketplace-api-log1")) implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common") - implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-logback") + implementation(libs.mkpl.logs.logback) implementation(project(":ok-marketplace-common")) diff --git a/ok-marketplace-be/ok-marketplace-biz/build.gradle.kts b/ok-marketplace-be/ok-marketplace-biz/build.gradle.kts index d29ec4c..ab4ccef 100644 --- a/ok-marketplace-be/ok-marketplace-biz/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-biz/build.gradle.kts @@ -10,7 +10,8 @@ kotlin { dependencies { implementation(kotlin("stdlib-common")) - implementation(libs.cor) + implementation(libs.mkpl.cor) + implementation(libs.mkpl.state.common) implementation(project(":ok-marketplace-common")) implementation(project(":ok-marketplace-stubs")) diff --git a/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/MkplAdProcessor.kt b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/MkplAdProcessor.kt index b7fb0b4..90a7225 100644 --- a/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/MkplAdProcessor.kt +++ b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/MkplAdProcessor.kt @@ -1,5 +1,7 @@ package ru.otus.otuskotlin.marketplace.biz +import ru.otus.otuskotlin.marketplace.biz.general.getAdState +import ru.otus.otuskotlin.marketplace.biz.general.getAdStates import ru.otus.otuskotlin.marketplace.biz.general.initStatus import ru.otus.otuskotlin.marketplace.biz.general.operation import ru.otus.otuskotlin.marketplace.biz.repo.* @@ -49,6 +51,7 @@ class MkplAdProcessor( repoPrepareCreate("Подготовка объекта для сохранения") repoCreate("Создание объявления в БД") } + getAdState("Вычисление состояния объявления") prepareResult("Подготовка ответа") } operation("Получить объявление", MkplCommand.READ) { @@ -75,6 +78,7 @@ class MkplAdProcessor( handle { adRepoDone = adRepoRead } } } + getAdState("Вычисление состояния объявления") prepareResult("Подготовка ответа") } operation("Изменить объявление", MkplCommand.UPDATE) { @@ -110,6 +114,7 @@ class MkplAdProcessor( repoPrepareUpdate("Подготовка объекта для обновления") repoUpdate("Обновление объявления в БД") } + getAdState("Вычисление состояния объявления") prepareResult("Подготовка ответа") } operation("Удалить объявление", MkplCommand.DELETE) { @@ -138,6 +143,7 @@ class MkplAdProcessor( repoPrepareDelete("Подготовка объекта для удаления") repoDelete("Удаление объявления из БД") } + getAdState("Вычисление состояния объявления") prepareResult("Подготовка ответа") } operation("Поиск объявлений", MkplCommand.SEARCH) { @@ -154,6 +160,7 @@ class MkplAdProcessor( finishAdFilterValidation("Успешное завершение процедуры валидации") } repoSearch("Поиск объявления в БД по фильтру") + getAdStates("Вычисление состояния объявления") prepareResult("Подготовка ответа") } operation("Поиск подходящих предложений для объявления", MkplCommand.OFFERS) { @@ -177,8 +184,8 @@ class MkplAdProcessor( repoPrepareOffers("Подготовка данных для поиска предложений") repoOffers("Поиск предложений для объявления в БД") } + getAdStates("Вычисление состояния объявления") prepareResult("Подготовка ответа") } }.build() } - diff --git a/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdState.kt b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdState.kt new file mode 100644 index 0000000..476d6ad --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdState.kt @@ -0,0 +1,17 @@ +package ru.otus.otuskotlin.marketplace.biz.general + +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.models.MkplState +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker + +fun ICorChainDsl.getAdState(title: String) = worker { + this.title = title + this.description = """ + Получаем состояние из сервиса состояний + """.trimIndent() + on { state == MkplState.RUNNING } + handle { + corSettings.stateSettings.stateMachine + } +} diff --git a/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdStates.kt b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdStates.kt new file mode 100644 index 0000000..4390d2b --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-biz/src/commonMain/kotlin/general/GetAdStates.kt @@ -0,0 +1,17 @@ +package ru.otus.otuskotlin.marketplace.biz.general + +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.models.MkplState +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker + +fun ICorChainDsl.getAdStates(title: String) = worker { + this.title = title + this.description = """ + Получаем состояние из сервиса состояний + """.trimIndent() + on { state == MkplState.RUNNING } + handle { + corSettings.stateSettings.stateMachine + } +} diff --git a/ok-marketplace-be/ok-marketplace-common/build.gradle.kts b/ok-marketplace-be/ok-marketplace-common/build.gradle.kts index 9b8d8d1..c103ffa 100644 --- a/ok-marketplace-be/ok-marketplace-common/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-common/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { api(libs.kotlinx.datetime) implementation(libs.coroutines.core) - api("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common") + api(libs.mkpl.state.common) } } val commonTest by getting { diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt index 7403212..1520a0d 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt @@ -5,6 +5,7 @@ import ru.otus.otuskotlin.marketplace.common.models.* import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd import ru.otus.otuskotlin.marketplace.common.stubs.MkplStubs import ru.otus.otuskotlin.marketplace.common.ws.IMkplWsSession +import ru.otus.otuskotlin.marketplace.states.common.models.MkplStateRq data class MkplContext( var command: MkplCommand = MkplCommand.NONE, @@ -33,6 +34,9 @@ data class MkplContext( var adRepoDone: MkplAd = MkplAd(), // Результат, полученный из БД var adsRepoDone: MutableList = mutableListOf(), + // Запрашиваем статус из модуля статистики + var adState: MkplStateRq = MkplStateRq(), + var adResponse: MkplAd = MkplAd(), var adsResponse: MutableList = mutableListOf(), ) diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt index 4ca406e..2127ade 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/MkplCorSettings.kt @@ -3,6 +3,7 @@ package ru.otus.otuskotlin.marketplace.common import ru.otus.otuskotlin.marketplace.common.repo.IRepoAd import ru.otus.otuskotlin.marketplace.common.ws.IMkplWsSessionRepo import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider +import ru.otus.otuskotlin.marketplace.states.common.MkplStatesCorSettings data class MkplCorSettings( val loggerProvider: MpLoggerProvider = MpLoggerProvider(), @@ -10,6 +11,7 @@ data class MkplCorSettings( val repoStub: IRepoAd = IRepoAd.NONE, val repoTest: IRepoAd = IRepoAd.NONE, val repoProd: IRepoAd = IRepoAd.NONE, + val stateSettings: MkplStatesCorSettings = MkplStatesCorSettings(), ) { companion object { val NONE = MkplCorSettings() diff --git a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt index 21ff626..9dc0efe 100644 --- a/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt +++ b/ok-marketplace-be/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt @@ -1,8 +1,6 @@ package ru.otus.otuskotlin.marketplace.common.models -import kotlinx.datetime.Instant -import ru.otus.otuskotlin.marketplace.common.NONE -import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdStates data class MkplAd( var id: MkplAdId = MkplAdId.NONE, @@ -16,9 +14,6 @@ data class MkplAd( val permissionsClient: MutableSet = mutableSetOf(), var adState: SMAdStates = SMAdStates.NONE, - var views: Int = 0, - var timePublished: Instant = Instant.NONE, - var timeUpdated: Instant = Instant.NONE, ) { fun deepCopy(): MkplAd = copy( permissionsClient = permissionsClient.toMutableSet(), diff --git a/ok-marketplace-be/settings.gradle.kts b/ok-marketplace-be/settings.gradle.kts index 6fbc432..1bf5c7c 100644 --- a/ok-marketplace-be/settings.gradle.kts +++ b/ok-marketplace-be/settings.gradle.kts @@ -35,7 +35,6 @@ include(":ok-marketplace-api-log1") include(":ok-marketplace-common") include(":ok-marketplace-biz") -include(":ok-marketplace-biz-state") include(":ok-marketplace-stubs") include(":ok-marketplace-app-common") diff --git a/ok-marketplace-states/build.gradle.kts b/ok-marketplace-states/build.gradle.kts index 2ecdb60..abbfd5c 100644 --- a/ok-marketplace-states/build.gradle.kts +++ b/ok-marketplace-states/build.gradle.kts @@ -6,7 +6,7 @@ plugins { alias(libs.plugins.muschko.java) apply false } -group = "ru.otus.otuskotlin.marketplace" +group = "ru.otus.otuskotlin.marketplace.state" version = "0.0.1" allprojects { diff --git a/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts b/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts index 3750eb4..0d70723 100644 --- a/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts +++ b/ok-marketplace-states/ok-marketplace-states-biz/build.gradle.kts @@ -10,7 +10,7 @@ kotlin { dependencies { implementation(kotlin("stdlib-common")) - implementation(libs.cor) + implementation(libs.mkpl.cor) implementation(libs.coroutines.core) implementation(projects.okMarketplaceStatesCommon) diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt index e8fa9e6..fbdbb85 100644 --- a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/MkplGetStateProcessor.kt @@ -5,6 +5,7 @@ import ru.otus.otuskotlin.marketplace.biz.statemachine.general.prepareResponse import ru.otus.otuskotlin.marketplace.biz.statemachine.general.readStateFromDb import ru.otus.otuskotlin.marketplace.biz.statemachine.helper.initStatus import ru.otus.otuskotlin.marketplace.biz.statemachine.stubs.stubNoCase +import ru.otus.otuskotlin.marketplace.biz.statemachine.stubs.stubSuccess import ru.otus.otuskotlin.marketplace.biz.statemachine.stubs.stubs import ru.otus.otuskotlin.marketplace.biz.statemachine.validation.finishValidation import ru.otus.otuskotlin.marketplace.biz.statemachine.validation.validateIdNotEmpty @@ -26,6 +27,7 @@ class MkplAdStateProcessor( initRepo("Инициализация репозитория") stubs("Обработка стабов") { + stubSuccess("Успешный сценарий") stubNoCase("Ошибка: запрошенный стаб недопустим") } validation { @@ -40,4 +42,3 @@ class MkplAdStateProcessor( prepareResponse("Подготовка ответа") }.build() } - diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubSuccess.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubSuccess.kt new file mode 100644 index 0000000..d747924 --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonMain/kotlin/stubs/StubSuccess.kt @@ -0,0 +1,31 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine.stubs + +import kotlinx.datetime.Clock +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.models.MkplAdStateId +import ru.otus.otuskotlin.marketplace.states.common.models.MkplState +import ru.otus.otuskotlin.marketplace.states.common.models.MkplStateRq +import ru.otus.otuskotlin.marketplace.states.common.models.MkplStubs +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdStates +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMTransition +import kotlin.time.Duration.Companion.days + +fun ICorChainDsl.stubSuccess(title: String) = worker { + this.title = title + this.description = """ + Обрабатываем сценарий стаба с успешным запросом, когда возвращается объект с состоянием + """.trimIndent() + on { stubCase == MkplStubs.SUCCESS && state == MkplState.RUNNING } + handle { + stateResponse = MkplStateRq( + adId = stateRequest.adId.takeIf { it != MkplAdStateId.NONE } ?: MkplAdStateId("123"), + oldState = SMAdStates.ACTUAL, + created = Clock.System.now() - 3.days, + views = 10, + transition = SMTransition.NONE + ) + state = MkplState.FINISHING + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-biz/src/commonTest/kotlin/SMAdStateBizTest.kt b/ok-marketplace-states/ok-marketplace-states-biz/src/commonTest/kotlin/SMAdStateBizTest.kt new file mode 100644 index 0000000..e41bace --- /dev/null +++ b/ok-marketplace-states/ok-marketplace-states-biz/src/commonTest/kotlin/SMAdStateBizTest.kt @@ -0,0 +1,48 @@ +package ru.otus.otuskotlin.marketplace.biz.statemachine + +import kotlinx.coroutines.test.runTest +import ru.otus.otuskotlin.marketplace.biz.statemachine.resolver.SMAdStateResolverDefault +import ru.otus.otuskotlin.marketplace.states.common.MkplStateContext +import ru.otus.otuskotlin.marketplace.states.common.MkplStatesCorSettings +import ru.otus.otuskotlin.marketplace.states.common.models.* +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdSignal +import ru.otus.otuskotlin.marketplace.states.common.statemachine.SMAdStates +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.days + +class SMAdStateBizTest { + + @Test + fun bizGetTest() = runTest { + val machine = SMAdStateResolverDefault() + val settings = MkplStatesCorSettings(stateMachine = machine) + val processor = MkplAdStateProcessor(corSettings = settings) + val ctx = MkplStateContext( + workMode = MkplWorkMode.STUB, + stubCase = MkplStubs.SUCCESS, + stateRequest = MkplStateRq( + adId = MkplAdStateId("some") + ) + ) + processor.exec(ctx) + assertEquals(SMAdStates.ACTUAL, ctx.stateResponse.oldState) + assertContentEquals(emptyList(), ctx.errors) + assertEquals(MkplState.FINISHING, ctx.state) + } + + @Test + fun new2hit() { + val machine = SMAdStateResolverDefault() + val signal = SMAdSignal( + state = SMAdStates.NEW, + duration = 2.days, + views = 101, + ) + val transition = machine.resolve(signal) + assertEquals(SMAdStates.HIT, transition.state) + assertContains(transition.description, "Очень", ignoreCase = true) + } +} diff --git a/ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts b/ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts index 9b8d8d1..e5fb186 100644 --- a/ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts +++ b/ok-marketplace-states/ok-marketplace-states-common/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { api(libs.kotlinx.datetime) implementation(libs.coroutines.core) - api("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common") + api(libs.mkpl.logs.common) } } val commonTest by getting { diff --git a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMTransition.kt b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMTransition.kt index 4f0c00b..68bf6cf 100644 --- a/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMTransition.kt +++ b/ok-marketplace-states/ok-marketplace-states-common/src/commonMain/kotlin/statemachine/SMTransition.kt @@ -6,5 +6,6 @@ data class SMTransition( ) { companion object { val ERROR = SMTransition(SMAdStates.ERROR, "Unprovided transition occurred") + val NONE = SMTransition(SMAdStates.NONE, "Empty Transition") } } diff --git a/ok-marketplace-tests/settings.gradle.kts b/ok-marketplace-tests/settings.gradle.kts index 9e4e18c..06116d9 100644 --- a/ok-marketplace-tests/settings.gradle.kts +++ b/ok-marketplace-tests/settings.gradle.kts @@ -24,6 +24,4 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } -//include(":ok-marketplace-api-v1-jackson") -//include(":ok-marketplace-api-v2-kmp") include(":ok-marketplace-e2e-be") From f2f2d2a8e53fa2c0795195f95aa1e30ab5743cff Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Sun, 26 May 2024 12:14:48 +0500 Subject: [PATCH 7/9] m8l1 Statemachine integration: Fix JDK version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88699cb..e6c6ab0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Validate Gradle wrapper From b7105b4a7fe0c0480a2c9fbfe480fef81f20089c Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Sun, 26 May 2024 12:37:59 +0500 Subject: [PATCH 8/9] m8l1 Statemachine integration: Fix JDK version 1 --- .github/workflows/build.yml | 2 +- gradle.properties | 2 +- ok-marketplace-be/gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e6c6ab0..7218afd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,5 +50,5 @@ jobs: - name: Run Tests uses: gradle/gradle-build-action@v2 with: - gradle-version: 8.6 + gradle-version: 8.7 arguments: check -i diff --git a/gradle.properties b/gradle.properties index 733f509..e757396 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ kotlin.code.style=official -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true #kotlin.native.cacheKind.linuxX64=none diff --git a/ok-marketplace-be/gradle.properties b/ok-marketplace-be/gradle.properties index 541bd1f..e1e62d5 100644 --- a/ok-marketplace-be/gradle.properties +++ b/ok-marketplace-be/gradle.properties @@ -1,6 +1,6 @@ kotlin.code.style=official kotlin.native.ignoreDisabledTargets=true kotlin.native.cacheKind.linuxX64=none -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 From 492cbac6ff9a9e05e4e6c2aaa2e9f8a75d3fada9 Mon Sep 17 00:00:00 2001 From: Sergey Okatov Date: Sun, 26 May 2024 13:35:44 +0500 Subject: [PATCH 9/9] m8l1 Statemachine integration: Fix JDK version 2 --- .github/workflows/build.yml | 2 +- ok-marketplace-tests/gradle.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7218afd..62e832e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: submodules: recursive token: ${{ secrets.PAT_TOKEN }} - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: java-version: '21' diff --git a/ok-marketplace-tests/gradle.properties b/ok-marketplace-tests/gradle.properties index 28db788..2feb974 100644 --- a/ok-marketplace-tests/gradle.properties +++ b/ok-marketplace-tests/gradle.properties @@ -1,3 +1,4 @@ kotlin.code.style=official kotlin.native.ignoreDisabledTargets=true +org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 #kotlin.native.cacheKind.linuxX64=none