Skip to content

Commit

Permalink
[refactor]cursor 기반 page 타입 변경 (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
dojinyou authored Dec 19, 2023
1 parent fad9611 commit b075bfe
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 89 deletions.
7 changes: 7 additions & 0 deletions src/main/kotlin/com/mjucow/eatda/common/dto/CursorPage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mjucow.eatda.common.dto

class CursorPage<T>(
val contents: List<T>,
val hasNext: Boolean,
val nextCursor: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import com.mjucow.eatda.domain.poplarstore.service.PopularStoreCommandService
import com.mjucow.eatda.domain.store.service.query.dto.StoreDetailDto
import com.mjucow.eatda.domain.store.service.query.dto.StoreDto
import com.mjucow.eatda.persistence.store.StoreRepository
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
import org.springframework.data.domain.SliceImpl
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand All @@ -16,14 +13,13 @@ class StoreQueryService(
val repository: StoreRepository,
val popularStoreCommandService: PopularStoreCommandService,
) {
fun findAllByCategoryAndCursor(id: Long? = null, categoryId: Long? = null, page: Pageable): Slice<StoreDto> {
fun findAllByCategoryAndCursor(id: Long? = null, categoryId: Long? = null, size: Int): List<StoreDto> {
return if (categoryId == null) {
repository.findAllByIdLessThanOrderByIdDesc(page, id).map(StoreDto::from)
repository.findAllByIdLessThanOrderByIdDesc(size, id).map(StoreDto::from)
} else {
// FIXME(cache): store 캐시 처리 이후 store 조회 개선하기
val storeIds = repository.findIdsByCategoryIdOrderByIdDesc(categoryId, page, id)
val stores = repository.findAllByIdInOrderByIdDesc(storeIds.content).map(StoreDto::from)
SliceImpl(stores, page, storeIds.hasNext())
val storeIds = repository.findIdsByCategoryIdOrderByIdDesc(categoryId, size, id)
repository.findAllByIdInOrderByIdDesc(storeIds).map(StoreDto::from)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.mjucow.eatda.persistence.store

import com.mjucow.eatda.domain.store.entity.Store
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice

interface StoreCustomRepository {
fun findIdsByCategoryIdOrderByIdDesc(categoryId: Long, page: Pageable, id: Long? = null): Slice<Long>
fun findAllByIdLessThanOrderByIdDesc(page: Pageable, id: Long? = null): Slice<Store>
fun findIdsByCategoryIdOrderByIdDesc(categoryId: Long, size: Int, id: Long? = null): List<Long>
fun findAllByIdLessThanOrderByIdDesc(size: Int, id: Long? = null): List<Store>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ import com.mjucow.eatda.domain.store.entity.Store
import com.mjucow.eatda.jooq.Tables.STORE
import com.mjucow.eatda.jooq.Tables.STORE_CATEGORY
import org.jooq.DSLContext
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
import org.springframework.data.domain.SliceImpl
import org.springframework.stereotype.Repository
import kotlin.math.min

@Repository
class StoreCustomRepositoryImpl(
private val db: DSLContext,
) : StoreCustomRepository {
override fun findIdsByCategoryIdOrderByIdDesc(categoryId: Long, page: Pageable, id: Long?): Slice<Long> {
override fun findIdsByCategoryIdOrderByIdDesc(categoryId: Long, size: Int, id: Long?): List<Long> {
val query = db.select(STORE_CATEGORY.STORE_ID)
.from(STORE_CATEGORY)
.where(STORE_CATEGORY.CATEGORY_ID.eq(categoryId))
Expand All @@ -23,33 +19,25 @@ class StoreCustomRepositoryImpl(
query.and(STORE_CATEGORY.STORE_ID.lessThan(id))
}

val result = query.orderBy(STORE_CATEGORY.STORE_ID.desc())
.limit(page.pageSize + 1)
return query.orderBy(STORE_CATEGORY.STORE_ID.desc())
.limit(size + 1)
.fetch()
.into(Long::class.java)

val content = result.subList(0, min(result.size, page.pageSize))
val hasNext = result.size > page.pageSize

return SliceImpl(content, page, hasNext)
.toList()
}

override fun findAllByIdLessThanOrderByIdDesc(page: Pageable, id: Long?): Slice<Store> {
override fun findAllByIdLessThanOrderByIdDesc(size: Int, id: Long?): List<Store> {
val query = db.select()
.from(STORE)

if (id != null) {
query.where(STORE.ID.lessThan(id))
}

val result = query.orderBy(STORE.ID.desc())
.limit(page.pageSize + 1)
return query.orderBy(STORE.ID.desc())
.limit(size + 1)
.fetch()
.into(Store::class.java)

val content = result.subList(0, min(result.size, page.pageSize))
val hasNext = result.size > page.pageSize

return SliceImpl(content, page, hasNext)
.toList()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mjucow.eatda.presentation.store

import com.mjucow.eatda.common.dto.CursorPage
import com.mjucow.eatda.domain.store.service.command.MenuCommandService
import com.mjucow.eatda.domain.store.service.command.StoreCommandService
import com.mjucow.eatda.domain.store.service.command.dto.MenuCreateCommand
Expand All @@ -11,9 +12,6 @@ import com.mjucow.eatda.domain.store.service.query.dto.MenuList
import com.mjucow.eatda.domain.store.service.query.dto.StoreDetailDto
import com.mjucow.eatda.domain.store.service.query.dto.StoreDto
import com.mjucow.eatda.presentation.common.ApiResponse
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
import org.springframework.data.web.PageableDefault
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
Expand All @@ -25,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import kotlin.math.min

@RestController
@RequestMapping("/api/v1/stores")
Expand All @@ -45,12 +44,16 @@ class StoreController(
@GetMapping
@ResponseStatus(HttpStatus.OK)
fun findAllByCategoryIdAndCursor(
@RequestParam("storeId", required = false) id: Long?,
@RequestParam("storeId", required = false) storeId: Long?,
@RequestParam("categoryId", required = false) categoryId: Long?,
@PageableDefault(size = 20) page: Pageable,
): ApiResponse<Slice<StoreDto>> {
val storeDtos = storeQueryService.findAllByCategoryAndCursor(id, categoryId, page)
return ApiResponse.success(storeDtos)
@RequestParam("size", required = false) pageSize: Int = 20,
): ApiResponse<CursorPage<StoreDto>> {
val results = storeQueryService.findAllByCategoryAndCursor(storeId, categoryId, pageSize)

val contents = results.subList(0, min(pageSize, results.size))
val hasNext = results.size > pageSize
val nextCursor = if (hasNext) contents.last().id.toString() else null
return ApiResponse.success(CursorPage(contents, hasNext, nextCursor))
}

@GetMapping("/{storeId}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Import
import org.springframework.data.domain.Pageable
import java.util.stream.IntStream

@Import(value = [StoreQueryService::class])
Expand Down Expand Up @@ -71,31 +70,28 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
val latestId = IntStream.range(0, pageSize * 2).mapToLong {
repository.save(Store(name = "validName$it", address = StoreMother.ADDRESS)).id
}.max().asLong
val page = Pageable.ofSize(pageSize)

// when
val result = storeQueryService.findAllByCategoryAndCursor(page = page)
val result = storeQueryService.findAllByCategoryAndCursor(size = pageSize)

// then
assertThat(result).anyMatch { it.id == latestId }
}

@DisplayName("데이터가 페이지 크기보다 크다면 페이지 크기만큼만 조회된다")
@DisplayName("데이터가 페이지 크기보다 크다면 페이지 크기 + 1만큼만 조회된다")
@Test
fun findAllByCursorWithPage() {
// given
val pageSize = Store.MAX_NAME_LENGTH - 1
repeat(Store.MAX_NAME_LENGTH) {
repository.save(Store(name = "x".repeat(it + 1), address = StoreMother.ADDRESS))
}
val page = Pageable.ofSize(pageSize)

// when
val result = storeQueryService.findAllByCategoryAndCursor(id = (pageSize * 2).toLong(), page = page)
val result = storeQueryService.findAllByCategoryAndCursor(id = (pageSize * 2).toLong(), size = pageSize)

// then
assertThat(result.content.size).isEqualTo(pageSize)
assertThat(result.hasNext()).isTrue()
assertThat(result.size).isEqualTo(pageSize + 1)
}

@DisplayName("조회할 결과가 일부라면 일부만 조회된다")
Expand All @@ -105,26 +101,24 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
val store = StoreMother.create()
val storeId = repository.save(store).id
val pageSize = 10
val page = Pageable.ofSize(pageSize)

// when
val result = storeQueryService.findAllByCategoryAndCursor(id = storeId + 1, page = page)
val result = storeQueryService.findAllByCategoryAndCursor(id = storeId + 1, size = pageSize)

// then
assertThat(result.content.size).isLessThan(pageSize)
assertThat(result.hasNext()).isFalse()
assertThat(result.size).isLessThan(pageSize)
}

@DisplayName("조회할 데이터가 없다면 empty를 반환한다")
@Test
fun findEmptyResult() {
// given
val pageSize = 10
val store = StoreMother.create()
repository.save(store)
val page = Pageable.ofSize(10)

// when
val result = storeQueryService.findAllByCategoryAndCursor(id = store.id, page = page)
val result = storeQueryService.findAllByCategoryAndCursor(id = store.id, size = pageSize)

// then
assertThat(result).isEmpty()
Expand All @@ -141,16 +135,15 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
store.addCategory(category)
repository.saveAndFlush(store).id
}.max().asLong
val page = Pageable.ofSize(pageSize)

// when
val result = storeQueryService.findAllByCategoryAndCursor(categoryId = category.id, page = page)
val result = storeQueryService.findAllByCategoryAndCursor(categoryId = category.id, size = pageSize)

// then
assertThat(result).anyMatch { it.id == latestId }
}

@DisplayName("데이터가 페이지 크기보다 크다면 페이지 크기만큼만 조회된다: 특정 카테고리")
@DisplayName("데이터가 페이지 크기보다 크다면 페이지 크기 + 1만큼만 조회된다: 특정 카테고리")
@Test
fun categoryTest2() {
// given
Expand All @@ -161,17 +154,15 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
store.addCategory(category)
repository.saveAndFlush(store)
}
val page = Pageable.ofSize(pageSize)

// when
val result = storeQueryService.findAllByCategoryAndCursor(
categoryId = category.id,
page = page
size = pageSize
)

// then
assertThat(result.content.size).isEqualTo(pageSize)
assertThat(result.hasNext()).isTrue()
assertThat(result.size).isEqualTo(pageSize + 1)
}

@DisplayName("조회할 결과가 일부라면 일부만 조회된다: 특정 카테고리")
Expand All @@ -183,18 +174,16 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
store.addCategory(category)
val storeId = repository.save(store).id
val pageSize = 10
val page = Pageable.ofSize(pageSize)

// when
val result = storeQueryService.findAllByCategoryAndCursor(
categoryId = category.id,
id = storeId + 1,
page = page
size = pageSize
)

// then
assertThat(result.content.size).isLessThan(pageSize)
assertThat(result.hasNext()).isFalse()
assertThat(result.size).isLessThan(pageSize)
}

@DisplayName("조회할 데이터가 없다면 empty를 반환한다: 특정 카테고리")
Expand All @@ -205,13 +194,12 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
val store = StoreMother.create()
store.addCategory(category)
repository.save(store)
val page = Pageable.ofSize(10)

// when
val result = storeQueryService.findAllByCategoryAndCursor(
categoryId = category.id,
id = store.id,
page = page
size = 10
)

// then
Expand Down
Loading

0 comments on commit b075bfe

Please sign in to comment.