add service layer with ProductService, add deleted_at and unique index to product table

This commit is contained in:
Denis Savosin
2024-09-30 12:40:47 +07:00
parent 2b07944b0e
commit f12839a15f
8 changed files with 82 additions and 6 deletions

View File

@@ -28,15 +28,12 @@ dependencies {
implementation("org.flywaydb:flyway-core") implementation("org.flywaydb:flyway-core")
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.postgresql:postgresql")
implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("org.springframework.boot:spring-boot-starter-mustache") implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("org.postgresql:postgresql")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")

View File

@@ -0,0 +1,3 @@
package com.example.demo.exceptions
class UnprocessableException(override val message: String): RuntimeException(message)

View File

@@ -27,6 +27,11 @@ data class Product(
@Serializable(with = OffsetDateTimeSerialization::class) @Serializable(with = OffsetDateTimeSerialization::class)
@Column(value = "updated_at") @Column(value = "updated_at")
val updatedAt: OffsetDateTime?, val updatedAt: OffsetDateTime?,
@Serializable(with = OffsetDateTimeSerialization::class)
@Column(value = "deleted_at")
val deletedAt: OffsetDateTime?,
) { ) {
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2) fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
fun isDeleted(): Boolean = deletedAt != null
} }

View File

@@ -1,13 +1,17 @@
package com.example.demo.provider package com.example.demo.provider
import com.example.demo.models.Product import com.example.demo.models.Product
import org.springframework.data.jpa.repository.Query import org.springframework.data.jdbc.repository.query.Query
import org.springframework.data.repository.CrudRepository import org.springframework.data.repository.CrudRepository
import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.time.OffsetDateTime
import java.util.* import java.util.*
@Repository @Repository
interface ProductRepository: CrudRepository<Product, Long> { interface ProductRepository: CrudRepository<Product, Long> {
@Query(value = "SELECT * FROM Product WHERE guid = :guid")
fun findByGuid(guid: UUID): Product? fun findByGuid(guid: UUID): Product?
@Query(value = "UPDATE Product SET deleted_at = :deletedAt WHERE guid = :guid RETURNING *")
fun softDelete(@Param("guid") guid: UUID, @Param("deletedAt") deletedAt: OffsetDateTime): Product?
} }

View File

@@ -0,0 +1,17 @@
package com.example.demo.services
import com.example.demo.exceptions.NotFoundException
import com.example.demo.exceptions.UnprocessableException
import com.example.demo.models.Product
import org.springframework.stereotype.Service
import java.util.*
@Service
interface ProductService {
fun findByGuid(guid: UUID): Product?
fun create(name: String, price: Long, description: String?): Product
@Throws(NotFoundException::class, UnprocessableException::class)
fun delete(guid: UUID): Product?
}

View File

@@ -0,0 +1,48 @@
package com.example.demo.services
import com.example.demo.exceptions.NotFoundException
import com.example.demo.exceptions.UnprocessableException
import com.example.demo.models.Product
import com.example.demo.provider.ProductRepository
import java.time.OffsetDateTime
import java.util.*
class ProductServiceImpl(private val productRepository: ProductRepository): ProductService {
override fun findByGuid(guid: UUID): Product? = productRepository.findByGuid(guid)
override fun create(name: String, price: Long, description: String?): Product {
val product = Product(
id = null,
guid = UUID.randomUUID(),
name = name,
description = description,
price = price,
createdAt = OffsetDateTime.now(),
updatedAt = null,
deletedAt = null,
)
return productRepository.save(product)
}
override fun delete(guid: UUID): Product? {
val product = findByGuid(guid) ?: throw NotFoundException()
if (product.isDeleted()) {
throw UnprocessableException("product already deleted")
}
val deletedProduct = product.copy(
id = product.id!!,
guid = product.guid,
name = product.name,
description = product.description,
price = product.price,
createdAt = product.createdAt,
updatedAt = product.updatedAt,
deletedAt = OffsetDateTime.now(),
)
return productRepository.save(deletedProduct)
}
}

View File

@@ -0,0 +1 @@
alter table product add deleted_at timestamptz default null;

View File

@@ -0,0 +1 @@
CREATE UNIQUE INDEX product_guid_idx ON product (guid);