Skip to content

Commit

Permalink
Merge pull request #3 from hyuksooon/feature/#1_item
Browse files Browse the repository at this point in the history
Feature/#1 item
  • Loading branch information
chs98412 authored Jun 17, 2024
2 parents e2deab1 + 3eddc58 commit a426f7f
Show file tree
Hide file tree
Showing 21 changed files with 296 additions and 62 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.springframework.boot:spring-boot-starter-validation")
}

tasks.withType<KotlinCompile> {
Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/com/soon/common/application/ItemCommandService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.soon.common.application

import com.soon.common.application.model.ItemCreateCommand
import com.soon.common.domain.item.Item
import com.soon.common.domain.item.ItemRepository
import org.springframework.stereotype.Service

@Service
class ItemCommandService(
private val itemRepository: ItemRepository,
) {
suspend fun createItem(command: ItemCreateCommand) {
itemRepository.save(Item.create(command.toCreateModel()))
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/soon/common/application/ItemQueryService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.soon.common.application

import com.soon.common.application.model.ItemGetSummary
import com.soon.common.domain.item.ItemRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service

@Service
class ItemQueryService(
private val itemRepository: ItemRepository,
) {
fun getItem(itemNo: Int): ItemGetSummary {
return itemRepository.findByIdOrNull(itemNo)?.let {
ItemGetSummary.of(it)
} ?: throw IllegalArgumentException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.soon.common.application.model

import com.soon.common.domain.item.model.ItemCreateModel

data class ItemCreateCommand(
val serviceNo: Int,
val title: String,
val description: String,
val thumbnail: String,
) {
fun toCreateModel() = ItemCreateModel(
serviceNo = serviceNo,
title = title,
description = description,
thumbnail = thumbnail,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.soon.common.application.model

import com.soon.common.domain.item.Item

data class ItemGetSummary(
val itemNo: Int,
val serviceNo: Int,
val title: String,
val description: String,
val thumbnail: String,
) {
companion object {
fun of(item: Item) = ItemGetSummary(
itemNo = item.no,
serviceNo = item.serviceNo,
title = item.title,
description = item.description,
thumbnail = item.thumbnail,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,4 @@ import kotlin.coroutines.CoroutineContext
val boundedElasticDispatcher= Schedulers.boundedElastic().asCoroutineDispatcher()

val boundedElasticScope:CoroutineScope
get()= CoroutineScope(boundedElasticDispatcher)

suspend fun <T> TransactionTemplate.executeWithContext(
context: CoroutineContext= boundedElasticDispatcher,
readOnly: Boolean=false,
block:CoroutineScope.() -> T,
): T= withContext(context){
this@executeWithContext.execute {
if(readOnly) TransactionSynchronizationManager.setCurrentTransactionReadOnly(true)
block()
} as T
}
get()= CoroutineScope(boundedElasticDispatcher)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import org.slf4j.LoggerFactory
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext

object GlobalCoroutineExceptionHandler: AbstractCoroutineContextElement(CoroutineExceptionHandler),
CoroutineExceptionHandler{
private val log = LoggerFactory.getLogger(javaClass)
object GlobalCoroutineExceptionHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
CoroutineExceptionHandler {
private val log = LoggerFactory.getLogger(javaClass)

override fun handleException(context: CoroutineContext, exception: Throwable) {
val name = context[CoroutineName]?.name ?: "coroutuine"
val description=context[CoroutineDescription]?.description
log.error("$name failed. : $description", exception)
for (suppressed in exception.suppressed) {
log.error("$name has suppressed error",suppressed)
}
override fun handleException(context: CoroutineContext, exception: Throwable) {
val name = context[CoroutineName]?.name ?: "coroutine"
val description = context[CoroutineDescription]?.description
log.error("$name failed. : $description", exception)
for (suppressed in exception.suppressed) {
log.error("$name has suppressed error", suppressed)
}
}
}
38 changes: 38 additions & 0 deletions src/main/kotlin/com/soon/common/domain/item/Item.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.soon.common.domain.item

import com.soon.common.domain.item.model.ItemCreateModel
import jakarta.persistence.*
import java.time.LocalDateTime

@Entity
@Table(name = "item")
class Item(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "item_no")
val no: Int = 0,

@Column(name = "service_no")
val serviceNo: Int,

@Column(name = "title")
val title: String,

@Column(name = "description")
val description: String,

@Column(name = "thumbnail")
val thumbnail: String,
) {
@Column(name = "created_at")
val createdAt: LocalDateTime = LocalDateTime.now()

companion object {
fun create(createModel: ItemCreateModel) = Item(
serviceNo = createModel.serviceNo,
title = createModel.title,
description = createModel.description,
thumbnail = createModel.thumbnail,
)
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/soon/common/domain/item/ItemRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.soon.common.domain.item

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

interface ItemRepository : JpaRepository<Item, Int>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.soon.common.domain.item.model

data class ItemCreateModel(
val serviceNo: Int,
val title: String,
val description: String,
val thumbnail: String,
)
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.soon.member.presentation.extension
package com.soon.common.presentation.extension

import com.fasterxml.jackson.annotation.JsonProperty


data class MemberHeader
(
@JsonProperty("no")
val no: Int,
@JsonProperty("name")
val name: String,
@JsonProperty("account")
val account: String,
@JsonProperty("no")
val no: Int,
@JsonProperty("name")
val name: String,
@JsonProperty("account")
val account: String,
)
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package com.soon.member.presentation.extension
package com.soon.common.presentation.extension

import com.soon.common.domain.extension.decodeBase64ToDto
import com.soon.common.presentation.extension.ServiceHeader
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.queryParamOrNull

fun ServerRequest.extractMemberCodeHeader() :MemberHeader{
fun ServerRequest.extractMemberCodeHeader(): MemberHeader {
return headers().header("Member-Code").firstOrNull()
?.let{
it.decodeBase64ToDto<MemberHeader>()
}?:throw IllegalArgumentException()
?.let {
it.decodeBase64ToDto<MemberHeader>()
} ?: throw IllegalArgumentException()
}

fun ServerRequest.extractServiceCodeHeader() :ServiceHeader{
fun ServerRequest.extractServiceCodeHeader(): ServiceHeader {
return headers().header("Service-Code").firstOrNull()
?.let{
it.decodeBase64ToDto<ServiceHeader>()
}?:throw IllegalArgumentException()
}
?.let {
it.decodeBase64ToDto<ServiceHeader>()
} ?: throw IllegalArgumentException()
}

fun ServerRequest.intQueryParam(parameter: String): Int {
return queryParamOrNull(parameter)?.toIntOrNull()
?: throw IllegalArgumentException("Invalid or missing 'itemNo' query parameter")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.soon.common.presentation.handler

import com.soon.common.application.ItemCommandService
import com.soon.common.application.ItemQueryService
import com.soon.common.application.util.coroutines.ApplicationDispatchers
import com.soon.common.presentation.extension.extractServiceCodeHeader
import com.soon.common.presentation.extension.intQueryParam
import com.soon.common.presentation.handler.model.ItemCreateRequest
import com.soon.common.presentation.handler.model.ItemGetResponse
import kotlinx.coroutines.withContext
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.server.*

@Service
class ItemHandler(
private val itemCommandService: ItemCommandService,
private val itemQueryService: ItemQueryService,
) {
suspend fun createItem(request: ServerRequest): ServerResponse = withContext(ApplicationDispatchers.IO) {
val serviceHeader = request.extractServiceCodeHeader()
val command = request.awaitBodyOrNull<ItemCreateRequest>()?.toCommand(serviceHeader.no)
?: throw IllegalArgumentException()

itemCommandService.createItem(command)
ServerResponse.noContent().buildAndAwait()
}

suspend fun getItem(request: ServerRequest): ServerResponse = withContext(ApplicationDispatchers.IO) {
val itemNo = request.intQueryParam("itemNo")
val summary = itemQueryService.getItem(itemNo)
ServerResponse.ok().bodyValueAndAwait(ItemGetResponse.of(summary))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.soon.common.presentation.handler.model

import com.soon.common.application.model.ItemCreateCommand
import jakarta.validation.constraints.NotBlank

data class ItemCreateRequest(
@field:NotBlank
val title: String,
@field:NotBlank
val description: String,
@field:NotBlank
val thumbnail: String,
) {
fun toCommand(serviceNo: Int) = ItemCreateCommand(
serviceNo = serviceNo,
title = title,
description = description,
thumbnail = thumbnail,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.soon.common.presentation.handler.model

import com.soon.common.application.model.ItemGetSummary

data class ItemGetResponse(
val itemNo: Int,
val serviceNo: Int,
val title: String,
val description: String,
val thumbnail: String,
) {
companion object {
fun of(summary: ItemGetSummary) = ItemGetResponse(
itemNo = summary.itemNo,
serviceNo = summary.serviceNo,
title = summary.title,
description = summary.description,
thumbnail = summary.thumbnail,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.soon.common.presentation.router.item

import com.soon.common.presentation.handler.ItemHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.coRouter

@Configuration
class ItemInternalRouter(private val itemHandler: ItemHandler) {
@Bean
fun itemInternalRoute(): RouterFunction<ServerResponse> {
return coRouter {
(accept(MediaType.APPLICATION_JSON) and "/internal/common/item").nest {
POST("", itemHandler::createItem)
GET("", itemHandler::getItem)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.soon.common.presentation.router.item

import com.soon.common.presentation.handler.ItemHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.coRouter

@Configuration
class ItemRouter(private val itemHandler: ItemHandler) {
@Bean
fun itemRoute(): RouterFunction<ServerResponse> {
return coRouter {
(accept(MediaType.APPLICATION_JSON) and "/common/item").nest {
GET("", itemHandler::getItem)
}
}
}
}
Loading

0 comments on commit a426f7f

Please sign in to comment.