diff --git a/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/domain/Item.kt b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/domain/Item.kt new file mode 100644 index 0000000..29577c3 --- /dev/null +++ b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/domain/Item.kt @@ -0,0 +1,8 @@ +package me.jiniworld.demohx.application.item.domain + +data class Item( + val id: String, + val name: String, + val price: Int, + val stock: Int, +) \ No newline at end of file diff --git a/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/input/GetItemQuery.kt b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/input/GetItemQuery.kt new file mode 100644 index 0000000..e28bfe0 --- /dev/null +++ b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/input/GetItemQuery.kt @@ -0,0 +1,9 @@ +package me.jiniworld.demohx.application.item.port.input + +import kotlinx.coroutines.flow.Flow +import me.jiniworld.demohx.application.item.domain.Item + + +interface GetItemQuery { + fun getItems(command: GetItemsCommand): Flow +} \ No newline at end of file diff --git a/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/input/GetItemsCommand.kt b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/input/GetItemsCommand.kt new file mode 100644 index 0000000..a3d8fb3 --- /dev/null +++ b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/input/GetItemsCommand.kt @@ -0,0 +1,6 @@ +package me.jiniworld.demohx.application.item.port.input + +data class GetItemsCommand( + val page: Int, + val size: Int, +) diff --git a/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/output/LoadItemPort.kt b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/output/LoadItemPort.kt new file mode 100644 index 0000000..a82adb9 --- /dev/null +++ b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/port/output/LoadItemPort.kt @@ -0,0 +1,11 @@ +package me.jiniworld.demohx.application.item.port.output + +import kotlinx.coroutines.flow.Flow +import me.jiniworld.demohx.application.item.domain.Item +import org.springframework.data.domain.Pageable + +interface LoadItemPort { + suspend fun loadItem(id: String): Item? + fun loadItems(pageable: Pageable): Flow + fun loadItems(ids: Collection): Flow +} \ No newline at end of file diff --git a/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/service/GetItemService.kt b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/service/GetItemService.kt new file mode 100644 index 0000000..80634f9 --- /dev/null +++ b/core/demo-reactive-core/src/main/kotlin/me/jiniworld/demohx/application/item/service/GetItemService.kt @@ -0,0 +1,25 @@ +package me.jiniworld.demohx.application.item.service + +import kotlinx.coroutines.flow.Flow +import me.jiniworld.demohx.annotation.UseCase +import me.jiniworld.demohx.application.item.domain.Item +import me.jiniworld.demohx.application.item.port.input.GetItemQuery +import me.jiniworld.demohx.application.item.port.input.GetItemsCommand +import me.jiniworld.demohx.application.item.port.output.LoadItemPort +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort +import org.springframework.transaction.annotation.Transactional + +@Transactional(readOnly = true) +@UseCase +internal class GetItemService( + private val loadItemPort: LoadItemPort, +): GetItemQuery { + + override fun getItems(command: GetItemsCommand): Flow { + return loadItemPort.loadItems( + PageRequest.of(command.page, command.size, Sort.by( + Sort.Order.desc("id"))) + ) + } +} \ No newline at end of file diff --git a/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemDocument.kt b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemDocument.kt new file mode 100644 index 0000000..dfc101a --- /dev/null +++ b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemDocument.kt @@ -0,0 +1,20 @@ +package me.jiniworld.demohx.persistence.item + +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.Id +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.mongodb.core.mapping.Document +import java.time.LocalDateTime + +@Document("item") +internal class ItemDocument(val name: String, val price: Int, var stock: Int) { + @Id + var id: String = "" + + @CreatedDate + var createdAt: LocalDateTime = LocalDateTime.now() + + @LastModifiedDate + var updatedAt: LocalDateTime? = null + +} \ No newline at end of file diff --git a/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemMapper.kt b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemMapper.kt new file mode 100644 index 0000000..de3aee6 --- /dev/null +++ b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemMapper.kt @@ -0,0 +1,11 @@ +package me.jiniworld.demohx.persistence.item + +import me.jiniworld.demohx.application.item.domain.Item + +internal object ItemMapper { + + fun mapToDomainEntity(doc: ItemDocument): Item { + return Item(doc.id, doc.name, doc.price, doc.stock) + } + +} \ No newline at end of file diff --git a/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemPersistenceAdapter.kt b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemPersistenceAdapter.kt new file mode 100644 index 0000000..a67e88e --- /dev/null +++ b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemPersistenceAdapter.kt @@ -0,0 +1,30 @@ +package me.jiniworld.demohx.persistence.item + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import me.jiniworld.demohx.annotation.PersistenceAdapter +import me.jiniworld.demohx.application.item.domain.Item +import me.jiniworld.demohx.application.item.port.output.LoadItemPort +import org.springframework.data.domain.Pageable + +@PersistenceAdapter +internal class ItemPersistenceAdapter( + private val itemRepository: ItemRepository, +): LoadItemPort { + + override suspend fun loadItem(id: String): Item? { + return itemRepository.findById(id) + ?.let { ItemMapper.mapToDomainEntity(it) } + } + + override fun loadItems(pageable: Pageable): Flow { + return itemRepository.findAllBy(pageable) + .map { ItemMapper.mapToDomainEntity(it) } + } + + override fun loadItems(ids: Collection): Flow { + return itemRepository.findAllById(ids) + .map { ItemMapper.mapToDomainEntity(it) } + } + +} \ No newline at end of file diff --git a/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemRepository.kt b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemRepository.kt new file mode 100644 index 0000000..2c9ec1e --- /dev/null +++ b/infrastructure/datastore-mongodb-reactive/src/main/kotlin/me/jiniworld/demohx/persistence/item/ItemRepository.kt @@ -0,0 +1,11 @@ +package me.jiniworld.demohx.persistence.item + +import kotlinx.coroutines.flow.Flow +import org.springframework.data.domain.Pageable +import org.springframework.data.repository.kotlin.CoroutineCrudRepository +import org.springframework.stereotype.Repository + +@Repository +internal interface ItemRepository: CoroutineCrudRepository { + fun findAllBy(pageable: Pageable): Flow +} \ No newline at end of file diff --git a/server/demo-reactive-app/src/main/kotlin/me/jiniworld/demohx/web/item/GetItemController.kt b/server/demo-reactive-app/src/main/kotlin/me/jiniworld/demohx/web/item/GetItemController.kt new file mode 100644 index 0000000..05fe8be --- /dev/null +++ b/server/demo-reactive-app/src/main/kotlin/me/jiniworld/demohx/web/item/GetItemController.kt @@ -0,0 +1,27 @@ +package me.jiniworld.demohx.web.item + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import me.jiniworld.demohx.annotation.WebAdapter +import me.jiniworld.demohx.application.item.port.input.GetItemQuery +import me.jiniworld.demohx.application.item.port.input.GetItemsCommand +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@WebAdapter +@Tag(name = "item", description = "상품") +@RestController +@RequestMapping("/v1/items") +internal class GetItemController( + private val getItemQuery: GetItemQuery, +) { + @Operation(summary = "삼품 목록") + @GetMapping("") + fun getItems( + @RequestParam(value = "page", required = false, defaultValue = "0") page: Int, + @RequestParam(value = "size", required = false, defaultValue = "10") size: Int, + ) = getItemQuery.getItems(GetItemsCommand(page = page, size = size)) + +} \ No newline at end of file