Skip to content

Commit

Permalink
wip(server): use postgresql
Browse files Browse the repository at this point in the history
  • Loading branch information
bas-kirill committed Aug 23, 2024
1 parent cb8582b commit 40a78a3
Show file tree
Hide file tree
Showing 22 changed files with 323 additions and 115 deletions.
3 changes: 2 additions & 1 deletion openapi/specs/instrument/GetInstrumentByIdEndpoint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ paths:
required: true
description: Instrument ID
schema:
type: string
type: integer
format: int64
responses:
"200":
description: Instrument Detail
Expand Down
2 changes: 2 additions & 0 deletions server/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2")
implementation("io.swagger.core.v3:swagger-annotations:2.2.22")
implementation("jakarta.validation:jakarta.validation-api:3.1.0") // `useSpringBoot3` param requires it
implementation("org.postgresql:postgresql")
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
}

tasks.named<Test>("test") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,112 +1,46 @@
package mu.muse.application.muse

import mu.muse.domain.IdGenerator
import mu.muse.domain.instrument.Country
import mu.muse.domain.instrument.Instrument
import mu.muse.domain.instrument.InstrumentId
import mu.muse.domain.instrument.InstrumentName
import mu.muse.domain.instrument.Manufacturer
import mu.muse.domain.instrument.ManufacturerDate
import mu.muse.domain.instrument.Material
import mu.muse.domain.instrument.ReleaseDate
import mu.muse.domain.user.FullName
import mu.muse.domain.user.Password
import mu.muse.domain.user.Role
import mu.muse.domain.user.User
import mu.muse.domain.user.UserId
import mu.muse.domain.user.Username
import mu.muse.persistence.instrument.InMemoryInstrumentIdGenerator
import mu.muse.persistence.instrument.InMemoryInstrumentRepository
import mu.muse.persistence.user.InMemoryUserRepository
import mu.muse.persistence.user.InMemoryUsernameIdGenerator
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import mu.muse.persistence.instrument.postgres.PostgresInstrumentIdGenerator
import mu.muse.persistence.instrument.postgres.PostgresInstrumentRepository
import mu.muse.persistence.user.postgres.PostgresUserIdGenerator
import mu.muse.persistence.user.postgres.PostgresUserRepository
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.crypto.password.PasswordEncoder
import java.time.Instant
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import javax.sql.DataSource

@Configuration
class PersistenceConfiguration {

@Bean
fun usernameIdGenerator() = InMemoryUsernameIdGenerator()
fun dataSource(): DataSource {
val hikariConfig = HikariConfig()
hikariConfig.username = System.getenv("POSTGRES_USER")
hikariConfig.password = System.getenv("POSTGRES_PASSWORD")
val pgDb = System.getenv("POSTGRES_DB")
hikariConfig.jdbcUrl = "jdbc:postgresql://localhost:5432/${pgDb}"
val hikariDatasource = HikariDataSource(hikariConfig)
// 25 is a good enough data pool size, it is a database in a container after all
hikariDatasource.maximumPoolSize = 25
return hikariDatasource
}

@Bean
fun users(idGenerator: IdGenerator<UserId>, passwordEncoder: PasswordEncoder): Set<User> {
val user = User.create(
id = idGenerator.generate(),
username = Username.from("user"),
password = Password.from(passwordEncoder.encode("123")),
role = Role.user(),
fullName = FullName.from("User Userov"),
)

val editor = User.create(
id = idGenerator.generate(),
username = Username.from("editor"),
password = Password.from(passwordEncoder.encode("321")),
role = Role.editor(),
fullName = FullName.from("Editor Editorov"),
)

return setOf(user, editor)
fun namedTemplate(dataSource: DataSource): NamedParameterJdbcTemplate {
return NamedParameterJdbcTemplate(dataSource)
}

@Bean
fun userRepository(users: Set<User>) = InMemoryUserRepository(users.associateBy { it.username }.toMutableMap())
fun userIdGenerator() = PostgresUserIdGenerator()

@Bean
fun instrumentIdGenerator() = InMemoryInstrumentIdGenerator()
fun userRepository(namedTemplate: NamedParameterJdbcTemplate) = PostgresUserRepository(namedTemplate)

@Bean
fun instruments(idGenerator: IdGenerator<InstrumentId>): Set<Instrument> {
val releasedGuitar = Instrument.create(
id = idGenerator.generate(),
name = InstrumentName.from("Fender Stratocaster"),
type = Instrument.Type.STRINGED,
manufacturer = Manufacturer.YAMAHA,
manufactureDate = ManufacturerDate.from(Instant.parse("2024-07-01T00:00:00Z")),
releaseDate = ReleaseDate.from(Instant.parse("2024-08-01T00:00:00Z")),
country = Country.CYPRUS,
materials = listOf(Material.WOOD),
)

val notYetReleasedGuitar = Instrument.create(
id = idGenerator.generate(),
name = InstrumentName.from("Fidel Telecastro"),
type = Instrument.Type.STRINGED,
manufacturer = Manufacturer.FENDER,
manufactureDate = ManufacturerDate.from(Instant.parse("2024-07-01T00:00:00Z")),
releaseDate = ReleaseDate.from(Instant.parse("2100-01-01T00:00:00Z")),
country = Country.CYPRUS,
materials = listOf(Material.WOOD),
)

val saxophone = Instrument.create(
id = idGenerator.generate(),
name = InstrumentName.from("SaxoStar"),
type = Instrument.Type.WIND,
manufacturer = Manufacturer.SIGMA,
manufactureDate = ManufacturerDate.from(Instant.parse("2007-01-01T00:00:00Z")),
releaseDate = ReleaseDate.from(Instant.parse("2008-07-01T00:00:00Z")),
country = Country.USA,
materials = listOf(Material.METALL),
)

val piano = Instrument.create(
id = idGenerator.generate(),
name = InstrumentName.from("Yamaha CLP-745B"),
type = Instrument.Type.KEYBOARD,
manufacturer = Manufacturer.YAMAHA,
manufactureDate = ManufacturerDate.from(Instant.parse("2007-01-01T00:00:00Z")),
releaseDate = ReleaseDate.from(Instant.parse("2008-07-01T00:00:00Z")),
country = Country.USA,
materials = listOf(Material.WOOD),
)

return setOf(notYetReleasedGuitar, releasedGuitar, saxophone, piano)
}
fun instrumentIdGenerator() = PostgresInstrumentIdGenerator()

@Bean
fun instrumentRepository(instruments: Set<Instrument>) =
InMemoryInstrumentRepository(instruments.associateBy { it.id }.toMutableMap())
fun instrumentRepository(namedTemplate: NamedParameterJdbcTemplate) = PostgresInstrumentRepository(namedTemplate)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ data class InstrumentId internal constructor(private val value: Long) {
fun toLongValue() = value

companion object {
fun from(valueStringRaw: String): InstrumentId {
val valueLongRaw = valueStringRaw.toLong()
fun from(valueLongRaw: Long): InstrumentId {
require(valueLongRaw >= 0 && valueLongRaw <= Long.MAX_VALUE)
return InstrumentId(valueLongRaw)
}
Expand Down
2 changes: 2 additions & 0 deletions server/app/src/main/kotlin/mu/muse/domain/user/UserId.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ class UserId internal constructor(private val value: Long) {
require(value >= 1L && value <= Long.MAX_VALUE)
return UserId(value)
}

fun zero() = UserId(0)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mu.muse.persistence.instrument
package mu.muse.persistence.instrument.inmemory

import mu.muse.domain.IdGenerator
import mu.muse.domain.instrument.InstrumentId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mu.muse.persistence.instrument
package mu.muse.persistence.instrument.inmemory

import mu.muse.domain.instrument.Instrument
import mu.muse.domain.instrument.InstrumentId
Expand Down Expand Up @@ -42,13 +42,13 @@ class InMemoryInstrumentRepository(
@Suppress("CyclomaticComplexMethod")
infix fun Instrument.matches(criteria: InstrumentExtractor.Criteria): Boolean =
(criteria.name == null || criteria.name.emptiness() || this.name.matches(criteria.name)) &&
(criteria.types == null || this.type in criteria.types) &&
(criteria.manufacturers == null || this.manufacturer in criteria.manufacturers) &&
this.manufactureDate.inRangeInclusive(
criteria.manufacturerDateFrom,
criteria.manufacturerDateTo,
) &&
this.releaseDate.inRangeInclusive(criteria.releaseDateFrom, criteria.releaseDateTo) &&
(criteria.countries == null || this.country in criteria.countries) &&
(criteria.materials == null || criteria.materials.containsAll(this.materials)) &&
(criteria.instrumentIds == null || criteria.instrumentIds.contains(this.id))
(criteria.types == null || this.type in criteria.types) &&
(criteria.manufacturers == null || this.manufacturer in criteria.manufacturers) &&
this.manufactureDate.inRangeInclusive(
criteria.manufacturerDateFrom,
criteria.manufacturerDateTo,
) &&
this.releaseDate.inRangeInclusive(criteria.releaseDateFrom, criteria.releaseDateTo) &&
(criteria.countries == null || this.country in criteria.countries) &&
(criteria.materials == null || criteria.materials.containsAll(this.materials)) &&
(criteria.instrumentIds == null || criteria.instrumentIds.contains(this.id))
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mu.muse.persistence.instrument.postgres

import mu.muse.domain.IdGenerator
import mu.muse.domain.instrument.InstrumentId
import org.springframework.jdbc.support.GeneratedKeyHolder

class PostgresInstrumentIdGenerator : IdGenerator<InstrumentId> {
private val keyHolder = GeneratedKeyHolder()

override fun generate(): InstrumentId {
return InstrumentId.from(keyHolder.key!!.toString())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package mu.muse.persistence.instrument.postgres

import mu.muse.common.types.Version
import mu.muse.domain.instrument.Country
import mu.muse.domain.instrument.Instrument
import mu.muse.domain.instrument.InstrumentId
import mu.muse.domain.instrument.InstrumentName
import mu.muse.domain.instrument.Manufacturer
import mu.muse.domain.instrument.ManufacturerDate
import mu.muse.domain.instrument.Material
import mu.muse.domain.instrument.ReleaseDate
import mu.muse.persistence.instrument.inmemory.matches
import mu.muse.usecase.access.instrument.InstrumentExtractor
import mu.muse.usecase.access.instrument.InstrumentPersister
import mu.muse.usecase.access.instrument.InstrumentRemover
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.jdbc.support.GeneratedKeyHolder
import java.sql.ResultSet

class PostgresInstrumentRepository(
private val namedTemplate: NamedParameterJdbcTemplate,
) : InstrumentExtractor, InstrumentRemover, InstrumentPersister {

private val keyHolder = GeneratedKeyHolder()

override fun findAll(): List<Instrument> {
val sql = "select " +
"instrument_id, " +
"instrument_name, " +
"instrument_type, " +
"manufacturer_name, " +
"manufacturer_date, " +
"release_date, " +
"country, " +
"materials " +
"from instruments"
return namedTemplate.query(sql) { rs, _ -> rs.toInstrument() }
}

override fun findById(id: InstrumentId): Instrument? {
val sql = "select " +
"instrument_id, " +
"instrument_name, " +
"instrument_type, " +
"manufacturer_name, " +
"manufacturer_date, " +
"release_date, " +
"country, " +
"materials " +
"from instruments " +
"where instrument_id = :instrument_id"
val params = MapSqlParameterSource().addValue("instrument_id", id.toLongValue())
return namedTemplate.queryForObject(sql, params) { rs, _ -> rs.toInstrument() }
}

override fun findByIds(ids: List<InstrumentId>): List<Instrument> {
val sql = "select " +
"instrument_id, " +
"instrument_name, " +
"instrument_type, " +
"manufacturer_name, " +
"manufacturer_date, " +
"release_date, " +
"country, " +
"materials " +
"from instruments " +
"where instrument_id in :instrument_ids"
val params = MapSqlParameterSource().addValue("instrument_ids", ids)
return namedTemplate.query(sql, params) { rs, _ -> rs.toInstrument() }
}

override fun findByCriteria(criteria: InstrumentExtractor.Criteria): List<Instrument> {
val sql = "select " +
"instrument_id, " +
"instrument_name, " +
"instrument_type, " +
"manufacturer_name, " +
"manufacturer_date, " +
"release_date, " +
"country, " +
"materials " +
"from instruments"
val instruments = namedTemplate.query(sql) { rs, _ -> rs.toInstrument() }
return instruments.filter { it matches criteria }
}

override fun totalElements(): Long {
val sql = "select count(*) from instruments"
return namedTemplate.queryForObject(sql, mapOf<String, Any>(), Long::class.java)!!
}

override fun removeInstrument(id: InstrumentId) {
val sql = "delete from instruments where instrument_id = :instrument_id"
val params = MapSqlParameterSource().addValue("instrument_id", id.toLongValue())
namedTemplate.update(sql, params, keyHolder)
}

override fun save(instrument: Instrument) {
val sql = "insert into instruments (instrument_name, instrument_type, manufacturer_name, manufacturer_date, release_date, country, materials) " +
"values (:instrument_name, :instrument_type, :manufacturer_name, :manufacturer_date, :release_date, :country, :materials)"
val params = MapSqlParameterSource()
.addValue("instrument_name", instrument.name.toStringValue())
.addValue("instrument_type", instrument.type.name)
.addValue("manufacturer_name", instrument.manufacturer.name)
.addValue("manufacturer_date", instrument.manufactureDate.toInstantValue())
.addValue("release_date", instrument.releaseDate.toInstantValue())
.addValue("country", instrument.country.name)
.addValue("materials", instrument.materials)
namedTemplate.update(sql, params)
}
}

fun ResultSet.toInstrument() = Instrument(
id = InstrumentId.from(this.getLong("instrument_id")),
name = InstrumentName.from(this.getString("instrument_name")),
type = Instrument.Type.valueOf(this.getString("instrument_type")),
manufacturer = Manufacturer.valueOf(this.getString("manufacturer_name")),
manufactureDate = ManufacturerDate.from(this.getTimestamp("manufacturer_date").toInstant()),
releaseDate = ReleaseDate.from(this.getTimestamp("release_date").toInstant()),
country = Country.valueOf(this.getString("country")),
materials = (this.getArray("materials").getArray() as Array<String>).toList().map { Material.valueOf(it) },
version = Version.new(),
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mu.muse.persistence.user
package mu.muse.persistence.user.inmemory

import mu.muse.domain.user.User
import mu.muse.domain.user.Username
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mu.muse.persistence.user
package mu.muse.persistence.user.inmemory

import mu.muse.domain.IdGenerator
import mu.muse.domain.user.UserId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package mu.muse.persistence.user.postgres

import mu.muse.domain.IdGenerator
import mu.muse.domain.user.UserId
import org.springframework.jdbc.support.GeneratedKeyHolder

class PostgresUserIdGenerator : IdGenerator<UserId> {
private val keyHolder = GeneratedKeyHolder()

override fun generate(): UserId {
return UserId.from(keyHolder.key!!.toLong())
}

}
Loading

0 comments on commit 40a78a3

Please sign in to comment.