move and refactor customer service to core

fix core dto serialization
This commit is contained in:
Savosin Denis
2025-05-12 14:13:52 +07:00
parent f92ba7795d
commit f4068f211e
7 changed files with 266 additions and 0 deletions

View File

@@ -1,14 +1,22 @@
package com.github.dannecron.demo.core.dto package com.github.dannecron.demo.core.dto
import com.github.dannecron.demo.db.serialialization.OffsetDateTimeSerialization
import com.github.dannecron.demo.db.serialialization.UuidSerialization
import kotlinx.serialization.Serializable
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.UUID import java.util.UUID
@Serializable
data class City( data class City(
val id: Long, val id: Long,
@Serializable(with = UuidSerialization::class)
val guid: UUID, val guid: UUID,
val name: String, val name: String,
@Serializable(with = OffsetDateTimeSerialization::class)
val createdAt: OffsetDateTime, val createdAt: OffsetDateTime,
@Serializable(with = OffsetDateTimeSerialization::class)
val updatedAt: OffsetDateTime?, val updatedAt: OffsetDateTime?,
@Serializable(with = OffsetDateTimeSerialization::class)
val deletedAt: OffsetDateTime?, val deletedAt: OffsetDateTime?,
) { ) {
fun isDeleted(): Boolean = deletedAt != null fun isDeleted(): Boolean = deletedAt != null

View File

@@ -0,0 +1,20 @@
package com.github.dannecron.demo.core.dto
import com.github.dannecron.demo.db.serialialization.OffsetDateTimeSerialization
import com.github.dannecron.demo.db.serialialization.UuidSerialization
import kotlinx.serialization.Serializable
import java.time.OffsetDateTime
import java.util.UUID
@Serializable
data class Customer(
val id: Long,
@Serializable(with = UuidSerialization::class)
val guid: UUID,
val name: String,
val cityId: Long?,
@Serializable(with = OffsetDateTimeSerialization::class)
val createdAt: OffsetDateTime,
@Serializable(with = OffsetDateTimeSerialization::class)
val updatedAt: OffsetDateTime?,
)

View File

@@ -1,18 +1,26 @@
package com.github.dannecron.demo.core.dto package com.github.dannecron.demo.core.dto
import com.github.dannecron.demo.db.serialialization.OffsetDateTimeSerialization
import com.github.dannecron.demo.db.serialialization.UuidSerialization
import kotlinx.serialization.Serializable
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.UUID import java.util.UUID
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Serializable
data class Product( data class Product(
val id: Long, val id: Long,
@Serializable(with = UuidSerialization::class)
val guid: UUID, val guid: UUID,
val name: String, val name: String,
val description: String?, val description: String?,
val price: Long, val price: Long,
@Serializable(with = OffsetDateTimeSerialization::class)
val createdAt: OffsetDateTime, val createdAt: OffsetDateTime,
@Serializable(with = OffsetDateTimeSerialization::class)
val updatedAt: OffsetDateTime?, val updatedAt: OffsetDateTime?,
@Serializable(with = OffsetDateTimeSerialization::class)
val deletedAt: OffsetDateTime?, val deletedAt: OffsetDateTime?,
) { ) {
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2) fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)

View File

@@ -0,0 +1,11 @@
package com.github.dannecron.demo.core.dto.view
import com.github.dannecron.demo.core.dto.City
import com.github.dannecron.demo.core.dto.Customer
import kotlinx.serialization.Serializable
@Serializable
data class CustomerExtended(
val customer: Customer,
val city: City?,
)

View File

@@ -0,0 +1,13 @@
package com.github.dannecron.demo.core.services.customer
import com.github.dannecron.demo.core.dto.Customer
import com.github.dannecron.demo.core.dto.view.CustomerExtended
import com.github.dannecron.demo.core.exceptions.CityNotFoundException
import java.util.UUID
interface CustomerService {
fun findByGuid(guid: UUID): CustomerExtended?
@Throws(CityNotFoundException::class)
fun create(name: String, cityGuid: UUID?): Customer
}

View File

@@ -0,0 +1,60 @@
package com.github.dannecron.demo.core.services.customer
import com.github.dannecron.demo.core.dto.City
import com.github.dannecron.demo.core.dto.Customer
import com.github.dannecron.demo.core.dto.view.CustomerExtended
import com.github.dannecron.demo.core.exceptions.CityNotFoundException
import com.github.dannecron.demo.core.services.generation.CommonGenerator
import com.github.dannecron.demo.db.entity.CityEntity
import com.github.dannecron.demo.db.entity.CustomerEntity
import com.github.dannecron.demo.db.repository.CityRepository
import com.github.dannecron.demo.db.repository.CustomerRepository
import org.springframework.stereotype.Service
import java.util.UUID
import kotlin.jvm.optionals.getOrNull
@Service
class CustomerServiceImpl(
private val customerRepository: CustomerRepository,
private val cityRepository: CityRepository,
private val commonGenerator: CommonGenerator,
) : CustomerService {
override fun findByGuid(guid: UUID): CustomerExtended? = customerRepository.findByGuid(guid)
?.let { customer ->
CustomerExtended(
customer = customer.toCore(),
city = customer.cityId?.let { cityId -> cityRepository.findById(cityId).getOrNull()?.toCore() }
)
}
@Throws(CityNotFoundException::class)
override fun create(name: String, cityGuid: UUID?): Customer = CustomerEntity(
id = null,
guid = commonGenerator.generateUUID(),
name = name,
cityId = cityGuid?.let {
cityRepository.findByGuid(it)?.id ?: throw CityNotFoundException()
},
createdAt = commonGenerator.generateCurrentTime(),
updatedAt = null,
).let(customerRepository::save)
.toCore()
private fun CustomerEntity.toCore() = Customer(
id = id!!,
guid = guid,
name = name,
cityId = cityId,
createdAt = createdAt,
updatedAt = updatedAt,
)
private fun CityEntity.toCore() = City(
id = id!!,
guid = guid,
name = name,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt,
)
}

View File

@@ -0,0 +1,146 @@
package com.github.dannecron.demo.core.services.customer
import com.github.dannecron.demo.core.dto.City
import com.github.dannecron.demo.core.dto.Customer
import com.github.dannecron.demo.core.dto.view.CustomerExtended
import com.github.dannecron.demo.core.exceptions.CityNotFoundException
import com.github.dannecron.demo.core.services.generation.CommonGenerator
import com.github.dannecron.demo.db.entity.CityEntity
import com.github.dannecron.demo.db.entity.CustomerEntity
import com.github.dannecron.demo.db.repository.CityRepository
import com.github.dannecron.demo.db.repository.CustomerRepository
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever
import java.time.OffsetDateTime
import java.util.Optional
import java.util.UUID
import kotlin.test.assertEquals
class CustomerServiceImplTest {
private val mockGuid = UUID.randomUUID()
private val mockCurrentTime = OffsetDateTime.now()
private val commonGenerator: CommonGenerator = mock {
on { generateUUID() } doReturn mockGuid
on { generateCurrentTime() } doReturn mockCurrentTime
}
private val customerRepository: CustomerRepository = mock()
private val cityRepository: CityRepository = mock()
private val customerServiceImpl = CustomerServiceImpl(
customerRepository = customerRepository,
cityRepository = cityRepository,
commonGenerator = commonGenerator,
)
private val cityId = 123L
private val cityGuid = UUID.randomUUID()
private val createdAt = OffsetDateTime.now()
private val customerEntity = CustomerEntity(
id = 1,
guid = mockGuid,
name = "name",
cityId = cityId,
createdAt = mockCurrentTime,
updatedAt = null,
)
private val customer = Customer(
id = 1,
guid = mockGuid,
name = "name",
cityId = cityId,
createdAt = mockCurrentTime,
updatedAt = null,
)
private val cityEntity = CityEntity(
id = cityId,
guid = cityGuid,
name = "city",
createdAt = createdAt,
updatedAt = null,
deletedAt = null,
)
private val city = City(
id = cityId,
guid = cityGuid,
name = "city",
createdAt = createdAt,
updatedAt = null,
deletedAt = null,
)
@Test
fun `create - success - with city`() {
whenever(customerRepository.save(any<CustomerEntity>())).thenReturn(customerEntity)
whenever(cityRepository.findByGuid(cityGuid)).thenReturn(cityEntity)
val result = customerServiceImpl.create("name", cityGuid)
assertEquals(customer, result)
verify(customerRepository, times(1)).save(customerEntity.copy(id = null))
verify(cityRepository, times(1)).findByGuid(cityGuid)
}
@Test
fun `create - success - no city`() {
val customerNoCityEntity = customerEntity.copy(cityId = null)
val customerNoCity = customer.copy(cityId = null)
whenever(customerRepository.save(any<CustomerEntity>())).thenReturn(customerNoCityEntity)
val result = customerServiceImpl.create("name", null)
assertEquals(customerNoCity, result)
verify(customerRepository, times(1)).save(customerNoCityEntity.copy(id = null))
verifyNoInteractions(cityRepository)
}
@Test
fun `create - fail - with city`() {
whenever(customerRepository.save(any<CustomerEntity>())).thenReturn(customerEntity)
whenever(cityRepository.findByGuid(cityGuid)).thenReturn(null)
assertThrows<CityNotFoundException> {
customerServiceImpl.create("name", cityGuid)
}
verify(customerRepository, never()).save(customerEntity.copy(id = null))
verify(cityRepository, times(1)).findByGuid(cityGuid)
}
@Test
fun `findByGuid - with city`() {
val customerGuid = mockGuid
whenever(customerRepository.findByGuid(any())).thenReturn(customerEntity)
whenever(cityRepository.findById(any())).thenReturn(Optional.of(cityEntity))
val result = customerServiceImpl.findByGuid(customerGuid)
assertEquals(CustomerExtended(customer, city), result)
verify(customerRepository, times(1)).findByGuid(customerGuid)
verify(cityRepository, times(1)).findById(cityId)
}
@Test
fun `findByGuid - no city`() {
val customerGuid = mockGuid
whenever(customerRepository.findByGuid(any())).thenReturn(customerEntity)
whenever(cityRepository.findById(any())).thenReturn(Optional.empty())
val result = customerServiceImpl.findByGuid(customerGuid)
assertEquals(CustomerExtended(customer, null), result)
verify(customerRepository, times(1)).findByGuid(customerGuid)
verify(cityRepository, times(1)).findById(cityId)
}
}