Skip to content

Commit

Permalink
m5l2 - Postgresql
Browse files Browse the repository at this point in the history
  • Loading branch information
evgnep committed Nov 9, 2023
1 parent 7253a92 commit d3b3019
Show file tree
Hide file tree
Showing 20 changed files with 415 additions and 11 deletions.
37 changes: 37 additions & 0 deletions deploy/docker-compose-spring-postgres.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
version: '3'
services:
psql:
image: postgres
container_name: postgresql
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
expose:
- "5432"
environment:
POSTGRES_PASSWORD: marketplace-pass
POSTGRES_USER: postgres
POSTGRES_DB: marketplace
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5

app:
image: ok-marketplace-app-spring
container_name: app-spring
ports:
- "8080:8080"
expose:
- "8080"
environment:
SQL_URL: jdbc:postgresql://psql:5432/marketplace
depends_on:
psql:
condition: service_healthy

volumes:
postgres_data:

3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ testContainersVersion=1.17.4
cache4kVersion=0.11.0
pluginShadow=7.1.2
yandexCloudSdkVersion=2.5.1
# PSQL
postgresDriverVersion=42.6.0
exposedVersion=0.41.1

# Kafka
kafkaVersion=3.4.0
2 changes: 1 addition & 1 deletion ok-marketplace-app-spring/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
// repo
implementation(project(":ok-marketplace-repo-in-memory"))
implementation(project(":ok-marketplace-repo-stubs"))
implementation(project(":ok-marketplace-repo-postgresql"))

// v1 api
implementation(project(":ok-marketplace-api-v1-jackson"))
Expand All @@ -55,7 +56,6 @@ dependencies {
implementation("com.sndyuk:logback-more-appenders:1.8.8")
implementation("org.fluentd:fluent-logger:0.3.4")


// tests
testImplementation("org.springframework.boot:spring-boot-starter-test")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,57 @@
package ru.otus.otuskotlin.markeplace.springapp.config

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings
import ru.otus.otuskotlin.marketplace.backend.repo.sql.RepoAdSQL
import ru.otus.otuskotlin.marketplace.backend.repo.sql.SqlProperties
import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub
import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor
import ru.otus.otuskotlin.marketplace.common.MkplCorSettings
import ru.otus.otuskotlin.marketplace.common.repo.IAdRepository
import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider
import ru.otus.otuskotlin.marketplace.logging.jvm.mpLoggerLogback
import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory

@Configuration
@EnableConfigurationProperties(SqlPropertiesEx::class)
class CorConfig {
@Bean
fun loggerProvider(): MpLoggerProvider = MpLoggerProvider { mpLoggerLogback(it) }

@Bean
fun processor() = MkplAdProcessor(corSettings())
fun processor(corSettings: MkplCorSettings) = MkplAdProcessor(corSettings)


@Bean
fun corSettings(): MkplCorSettings = MkplCorSettings(
fun corSettings(
@Qualifier("prodRepository") prodRepository: IAdRepository?,
@Qualifier("testRepository") testRepository: IAdRepository,
@Qualifier("stubRepository") stubRepository: IAdRepository,
): MkplCorSettings = MkplCorSettings(
loggerProvider = loggerProvider(),
repoStub = AdRepoStub(),
repoProd = AdRepoInMemory(),
repoTest = AdRepoInMemory(),
repoStub = stubRepository,
repoTest = testRepository,
repoProd = prodRepository ?: testRepository,
)

@Bean
fun appSettings(corSettings: MkplCorSettings) = MkplAppSettings(
processor = processor(),
fun appSettings(corSettings: MkplCorSettings, processor: MkplAdProcessor) = MkplAppSettings(
processor = processor,
logger = loggerProvider(),
corSettings = corSettings
)

@Bean(name = ["prodRepository"])
@ConditionalOnProperty(value = ["prod-repository"], havingValue = "sql")
fun prodRepository(sqlProperties: SqlProperties) = RepoAdSQL(sqlProperties)

@Bean
fun testRepository() = AdRepoInMemory()

@Bean
fun stubRepository() = AdRepoStub()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.otus.otuskotlin.markeplace.springapp.config

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.ConstructorBinding
import ru.otus.otuskotlin.marketplace.backend.repo.sql.SqlProperties

//Annotation @ConstructorBinding is needed, it cannot be placed over the c @Bean method, one way as workaround
@ConfigurationProperties("sql")
class SqlPropertiesEx @ConstructorBinding constructor(
url: String,
user: String,
password: String,
schema: String,
dropDatabase: Boolean
) : SqlProperties(url, user, password, schema, dropDatabase)
10 changes: 10 additions & 0 deletions ok-marketplace-app-spring/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ springdoc:
url: /specs-ad-v1.yaml
- name: v2
url: /specs-ad-v2.yaml

prod-repository: ${PROD_REPOSITORY:test}

sql:
url: ${SQL_URL:jdbc:postgresql://localhost:5432/marketplace}
user: ${SQL_USER:postgres}
password: ${SQL_PASSWORD:marketplace-pass}
schema: ${SQL_SCHEMA:marketplace}
drop-database: ${SQL_DROP_DATABASE:false}
fast-migration: ${SQL_FAST_MIGRATION:true}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package ru.otus.otuskotlin.markeplace.springapp

import com.ninjasquad.springmockk.MockkBean
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import ru.otus.otuskotlin.marketplace.backend.repo.sql.RepoAdSQL

@SpringBootTest
class ApplicationTests {
@MockkBean
private lateinit var repo: RepoAdSQL

@Test
fun contextLoads() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.web.reactive.function.BodyInserters
import ru.otus.otuskotlin.marketplace.api.v1.models.*
import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings
import ru.otus.otuskotlin.marketplace.backend.repo.sql.RepoAdSQL
import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor
import ru.otus.otuskotlin.marketplace.common.MkplContext
import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider
Expand All @@ -35,6 +36,9 @@ internal class AdControllerTest {
every { appSettings.logger } returns MpLoggerProvider()
}

@MockkBean
private lateinit var repo: RepoAdSQL

@Test
fun createAd() = testStubAd(
"/v1/ad/create",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.otus.otuskotlin.markeplace.springapp.api.v1.controller

import com.ninjasquad.springmockk.MockkBean
import io.kotest.common.runBlocking
import jakarta.websocket.*
import kotlinx.coroutines.Dispatchers
Expand All @@ -14,6 +15,7 @@ import ru.otus.otuskotlin.marketplace.api.v1.apiV1Mapper
import ru.otus.otuskotlin.marketplace.api.v1.models.AdInitResponse
import ru.otus.otuskotlin.marketplace.api.v1.models.IResponse
import ru.otus.otuskotlin.marketplace.api.v1.models.ResponseResult
import ru.otus.otuskotlin.marketplace.backend.repo.sql.RepoAdSQL
import java.net.URI


Expand All @@ -23,6 +25,9 @@ class WsControllerTest {
@LocalServerPort
private var port: Int = 0

@MockkBean
private lateinit var repo: RepoAdSQL

private lateinit var container: WebSocketContainer
private lateinit var client: TestWebSocketClient

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,14 @@ fun errorRepoConcurrency(
message = "The object has been changed concurrently by another user or process",
exception = exception ?: RepoConcurrencyException(expectedLock, actualLock),
)

val errorNotFound = MkplError(
field = "id",
message = "Not Found",
code = "not-found"
)

val errorEmptyId = MkplError(
field = "id",
message = "Id must not be null or blank"
)
17 changes: 15 additions & 2 deletions ok-marketplace-common/src/commonMain/kotlin/repo/DbAdResponse.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package ru.otus.otuskotlin.marketplace.common.repo

import ru.otus.otuskotlin.marketplace.common.helpers.errorEmptyId as mkplErrorEmptyId
import ru.otus.otuskotlin.marketplace.common.helpers.errorNotFound as mkplErrorNotFound
import ru.otus.otuskotlin.marketplace.common.helpers.errorRepoConcurrency
import ru.otus.otuskotlin.marketplace.common.models.MkplAd
import ru.otus.otuskotlin.marketplace.common.models.MkplAdLock
import ru.otus.otuskotlin.marketplace.common.models.MkplError

data class DbAdResponse(
Expand All @@ -12,7 +16,16 @@ data class DbAdResponse(
companion object {
val MOCK_SUCCESS_EMPTY = DbAdResponse(null, true)
fun success(result: MkplAd) = DbAdResponse(result, true)
fun error(errors: List<MkplError>) = DbAdResponse(null, false, errors)
fun error(error: MkplError) = DbAdResponse(null, false, listOf(error))
fun error(errors: List<MkplError>, data: MkplAd? = null) = DbAdResponse(data, false, errors)
fun error(error: MkplError, data: MkplAd? = null) = DbAdResponse(data, false, listOf(error))

val errorEmptyId = error(mkplErrorEmptyId)

fun errorConcurrent(lock: MkplAdLock, ad: MkplAd?) = error(
errorRepoConcurrency(lock, ad?.lock?.let { MkplAdLock(it.asString()) }),
ad
)

val errorNotFound = error(mkplErrorNotFound)
}
}
25 changes: 25 additions & 0 deletions ok-marketplace-repo-postgresql/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
kotlin("jvm")
}

dependencies {
val exposedVersion: String by project
val postgresDriverVersion: String by project
val kmpUUIDVersion: String by project
val testContainersVersion: String by project

implementation(kotlin("stdlib"))

implementation(project(":ok-marketplace-common"))

implementation("org.postgresql:postgresql:$postgresDriverVersion")

implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
implementation("com.benasher44:uuid:$kmpUUIDVersion")


testImplementation("org.testcontainers:postgresql:$testContainersVersion")
testImplementation(project(":ok-marketplace-repo-tests"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ru.otus.otuskotlin.marketplace.backend.repo.sql

import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import ru.otus.otuskotlin.marketplace.common.models.*

object AdTable : Table("ad") {
val id = varchar("id", 128)
val title = varchar("title", 128)
val description = varchar("description", 512)
val owner = varchar("owner", 128)
val visibility = enumeration("visibility", MkplVisibility::class)
val dealSide = enumeration("deal_side", MkplDealSide::class)
val lock = varchar("lock", 50)

override val primaryKey = PrimaryKey(id)

fun from(res: InsertStatement<Number>) = MkplAd(
id = MkplAdId(res[id].toString()),
title = res[title],
description = res[description],
ownerId = MkplUserId(res[owner].toString()),
visibility = res[visibility],
adType = res[dealSide],
lock = MkplAdLock(res[lock])
)

fun from(res: ResultRow) = MkplAd(
id = MkplAdId(res[id].toString()),
title = res[title],
description = res[description],
ownerId = MkplUserId(res[owner].toString()),
visibility = res[visibility],
adType = res[dealSide],
lock = MkplAdLock(res[lock])
)

fun to(it: UpdateBuilder<*>, ad: MkplAd, randomUuid: () -> String) {
it[id] = ad.id.takeIf { it != MkplAdId.NONE }?.asString() ?: randomUuid()
it[title] = ad.title
it[description] = ad.description
it[owner] = ad.ownerId.asString()
it[visibility] = ad.visibility
it[dealSide] = ad.adType
it[lock] = ad.lock.takeIf { it != MkplAdLock.NONE }?.asString() ?: randomUuid()
}

}
Loading

0 comments on commit d3b3019

Please sign in to comment.