Skip to content

Commit

Permalink
feat: 배경 구매및 조회 기능을 구현한다
Browse files Browse the repository at this point in the history
  • Loading branch information
devxb committed Oct 27, 2024
1 parent 2bc9214 commit 0fa9614
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/main/kotlin/org/gitanimals/shop/app/BuyBackgroundFacade.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.gitanimals.shop.app

import org.gitanimals.shop.domain.SaleService
import org.gitanimals.shop.domain.SaleType
import org.rooftop.netx.api.Orchestrator
import org.rooftop.netx.api.OrchestratorFactory
import org.springframework.stereotype.Service
import java.util.*

@Service
class BuyBackgroundFacade(
orchestratorFactory: OrchestratorFactory,
identityApi: IdentityApi,
renderApi: RenderApi,

private val saleService: SaleService,
) {

private lateinit var backgroundBuyOrchestrator: Orchestrator<String, Unit>

fun buyBackground(token: String, item: String) {
backgroundBuyOrchestrator.sagaSync(
item,
mapOf("token" to token, "idempotencyKey" to UUID.randomUUID().toString())
).decodeResultOrThrow(Unit::class)
}

init {
backgroundBuyOrchestrator = orchestratorFactory.create<String>("buy background facade")
.start({
val sale = saleService.getByTypeAndItem(SaleType.BACKGROUND, it)

require(sale.getCount() > 0) {
"Cannot buy item : \"${sale.type}\" cause its count : \"${sale.getCount()}\" == 0"
}

sale
})
.joinWithContext(
contextOrchestrate = { context, sale ->
val token = context.decodeContext("token", String::class)
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)
identityApi.decreasePoint(token, idempotencyKey, sale.price.toString())

sale
},
contextRollback = { context, sale ->
val token = context.decodeContext("token", String::class)
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)
identityApi.increasePoint(token, idempotencyKey, sale.price.toString())
}
)
.joinWithContext(
contextOrchestrate = { context, sale ->
val token = context.decodeContext("token", String::class)
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)

renderApi.addBackground(token, idempotencyKey, sale.item)
sale
},
contextRollback = { context, sale ->
val token = context.decodeContext("token", String::class)
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)

renderApi.deleteBackground(token, idempotencyKey, sale.item)
}
)
.commit { sale ->
saleService.buyBySaleTypeAndItem(sale.type, sale.item)
}
}
}
4 changes: 4 additions & 0 deletions src/main/kotlin/org/gitanimals/shop/app/RenderApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ interface RenderApi {

fun getPersonaById(token: String, personaId: Long): PersonaResponse

fun addBackground(token: String, idempotencyKey: String, backgroundName: String)

fun deleteBackground(token: String, idempotencyKey: String, backgroundName: String)

fun deletePersonaById(token: String, personaId: Long)

fun addPersona(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.gitanimals.shop.controller

import org.gitanimals.shop.app.BuyBackgroundFacade
import org.gitanimals.shop.controller.request.BuyBackgroundRequest
import org.gitanimals.shop.controller.response.BackgroundResponse
import org.gitanimals.shop.domain.SaleService
import org.gitanimals.shop.domain.SaleType
import org.springframework.http.HttpHeaders
import org.springframework.web.bind.annotation.*

@RestController
class BuySaleController(
private val saleService: SaleService,
private val buyBackgroundFacade: BuyBackgroundFacade,
) {

@GetMapping("/shops/backgrounds")
fun getBackgrounds(): BackgroundResponse =
BackgroundResponse.from(saleService.findAllByType(SaleType.BACKGROUND))


@PostMapping("/shops/backgrounds")
fun buyBackground(
@RequestHeader(HttpHeaders.AUTHORIZATION) token: String,
@RequestBody buyBackgroundRequest: BuyBackgroundRequest
) {
buyBackgroundFacade.buyBackground(token, buyBackgroundRequest.type)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.gitanimals.shop.controller.request

data class BuyBackgroundRequest(
val type: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.gitanimals.shop.controller.response

import org.gitanimals.shop.domain.Sale

data class BackgroundResponse(
val backgrounds: List<Background>,
) {

data class Background(
val type: String,
val price: String,
)

companion object {
fun from(sales: List<Sale>): BackgroundResponse {
return BackgroundResponse(
sales.map {
Background(
type = it.item,
price = it.price.toString(),
)
}.toList()
)
}
}
}
44 changes: 44 additions & 0 deletions src/main/kotlin/org/gitanimals/shop/domain/Sale.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.gitanimals.shop.domain

import jakarta.persistence.*
import org.gitanimals.shop.core.AggregateRoot

@AggregateRoot
@Entity(name = "sale")
@Table(
name = "sale", indexes = [
Index(name = "sale_idx_type", columnList = "type", unique = true)
]
)
class Sale(
@Id
@Column(name = "id")
val id: Long,

@Enumerated
@Column(name = "type", nullable = false, unique = true)
val type: SaleType,

@Column(name = "item", nullable = false)
val item: String,

@Column(name = "price", nullable = false)
val price: Long,

@Column(name = "count", nullable = false)
private var count: Long,

@Version
private var version: Long? = null,
) {

fun getCount(): Long = this.count

fun buy() {
require(this.count > 0) {
"Cannot buy item : \"$type\" cause its count : \"$count\" == 0"
}

this.count -= 1
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/org/gitanimals/shop/domain/SaleRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.gitanimals.shop.domain

import org.springframework.data.jpa.repository.JpaRepository

interface SaleRepository : JpaRepository<Sale, Long> {
fun getByItem(item: String): Sale

fun findAllByType(saleType: SaleType): List<Sale>
}
33 changes: 33 additions & 0 deletions src/main/kotlin/org/gitanimals/shop/domain/SaleService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.gitanimals.shop.domain

import org.springframework.orm.ObjectOptimisticLockingFailureException
import org.springframework.retry.annotation.Retryable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional(readOnly = true)
class SaleService(
private val saleRepository: SaleRepository,
) {

fun findAllByType(saleType: SaleType): List<Sale> = saleRepository.findAllByType(saleType)

@Transactional
@Retryable(ObjectOptimisticLockingFailureException::class)
fun buyBySaleTypeAndItem(saleType: SaleType, item: String) {
val sale = getByTypeAndItem(saleType, item)

sale.buy()
}

fun getByTypeAndItem(saleType: SaleType, item: String): Sale {
val sale = saleRepository.getByItem(item)

require(sale.type == saleType) {
"Cannot find sale by type: \"$saleType\" and item: \"$item\""
}

return sale
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/org/gitanimals/shop/domain/SaleType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.gitanimals.shop.domain

enum class SaleType {

BACKGROUND,
;
}
20 changes: 20 additions & 0 deletions src/main/kotlin/org/gitanimals/shop/infra/RestRenderApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ class RestRenderApi(
}
}

override fun addBackground(token: String, idempotencyKey: String, backgroundName: String) {
return restClient.post()
.uri("/internals/backgrounds?name=$backgroundName")
.header(HttpHeaders.AUTHORIZATION, token)
.header("Internal-Secret", internalSecret)
.exchange { _, response ->
require(response.statusCode.is2xxSuccessful) { "Cannot add background by backgroundName: \"$backgroundName\"" }
}
}

override fun deleteBackground(token: String, idempotencyKey: String, backgroundName: String) {
return restClient.delete()
.uri("/internals/backgrounds?name=$backgroundName")
.header(HttpHeaders.AUTHORIZATION, token)
.header("Internal-Secret", internalSecret)
.exchange { _, response ->
require(response.statusCode.is2xxSuccessful) { "Cannot delete background by backgroundName: \"$backgroundName\"" }
}
}

override fun addPersona(
token: String,
idempotencyKey: String,
Expand Down

0 comments on commit 0fa9614

Please sign in to comment.