Skip to content

Commit

Permalink
[๐ŸŒŽ Feature] ํƒ€์ž„ ๋ผ์ธ์šฉ ๋ชฉํ‘œ ์กฐํšŒ API ๊ฐœ๋ฐœ - Pagination ๋ฐฉ์‹ offset ๊ธฐ๋ฐ˜์œผ๋กœ ๋ณ€๊ฒฝ (#243)
Browse files Browse the repository at this point in the history
* delete: ์œ ์ € ์ •๋ณด๋ฅผ ํ™œ์šฉํ•œ ํƒ€์ž„๋ผ์ธ ์กฐํšŒ API ์‚ญ์ œ

* refactor: PaginationResult.transform() ์‚ฌ์šฉ ๋ฐฉ์‹ lamda ํ˜•ํƒœ๋กœ ํ†ต์ผ

* refactor: ๋ฉ”์„œ๋“œ ๋กœ์ง์— ๋”ฐ๋ผ get, find ๋ช…์นญ ๊ตฌ๋ถ„

* refactor: lamda body ๋‚ด ์ƒ๋žต ๊ฐ€๋Šฅํ•œ parameter ์ƒ๋žต

* refactor: ๋ฉ”์„œ๋“œ๋ช… ๋‚ด ๋ถ€์ •ํ™•ํ•œ ๋‹จ์–ด ์ œ๊ฑฐ(findGoalTimelineCountMap -> findGoalCountMap)

* refactor: associateWith ๋ฉ”์„œ๋“œ ํ™œ์šฉ

* refactor: PaginationResult total๊ฐ’ Long์œผ๋กœ ๋‹ค์‹œ ๋ณ€๊ฒฝ

* fix: ๋ชฉํ‘œ ํ”ผ๋“œ ์กฐํšŒ API deadline ์—ญ์ˆœ์œผ๋กœ ์ •๋ ฌ ์กฐํšŒ

* chore: apply lint

* fix: goals ํ…Œ์ด๋ธ”์˜ index ์ˆ˜์ •

* refactor: pagination cusorId type Any -> Generic์œผ๋กœ ๋ณ€๊ฒฝ

* fix: ํƒ€์ž„ ๋ผ์ธ ๋ชฉํ‘œ ์กฐํšŒ API paging ์ฒ˜๋ฆฌ ๋ฐฉ์‹ offset์œผ๋กœ ๋ณ€๊ฒฝ

* refactor: cursor ๊ธฐ๋ฐ˜ pagination ๊ณตํ†ต ๋ชจ๋“ˆ cursor type long์œผ๋กœ ํ†ต์ผ

* refactor: pagination ๊ณตํ†ต ๋ชจ๋“ˆ cursor์™€ offset ๊ตฌ๋ถ„

* refactor: ๋ฏธ์‚ฌ์šฉ ์ฝ”๋“œ ๋ฐ ์˜๋ฏธ ์—†๋Š” ์ค‘๋ณต ์ฝ”๋“œ ์‚ญ์ œ
  • Loading branch information
egg528 authored Mar 28, 2024
1 parent dc8a0dc commit ca23a69
Show file tree
Hide file tree
Showing 15 changed files with 64 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.raemian.api.cheer.model.CheererResult
import io.raemian.api.cheer.model.CheeringCountResult
import io.raemian.api.cheer.service.CheeringService
import io.raemian.api.support.response.ApiResponse
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.CursorPaginationResult
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
Expand All @@ -25,7 +25,7 @@ class CheeringController(
fun findCheeringSquad(
@PathVariable("lifeMapId") lifeMapId: Long,
request: CheeringSquadPageRequest,
): ResponseEntity<ApiResponse<PaginationResult<Long, CheererResult>>> =
): ResponseEntity<ApiResponse<CursorPaginationResult<CheererResult>>> =
ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request)))

@GetMapping("/count/{userName}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import io.raemian.api.event.model.CheeredEvent
import io.raemian.api.support.exception.CoreApiException
import io.raemian.api.support.exception.ErrorInfo
import io.raemian.api.support.limiter.CheeringLimiter
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.CursorPaginationResult
import io.raemian.storage.db.core.cheer.CheerJdbcQueryRepository
import io.raemian.storage.db.core.cheer.Cheerer
import io.raemian.storage.db.core.cheer.CheererRepository
import io.raemian.storage.db.core.cheer.Cheering
import io.raemian.storage.db.core.cheer.CheeringRepository
import io.raemian.storage.db.core.cheer.model.CheererQueryResult
import io.raemian.storage.db.core.common.pagination.CursorPaginationResult
import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate
import io.raemian.storage.db.core.common.pagination.PaginationResult
import io.raemian.storage.db.core.lifemap.LifeMapRepository
import io.raemian.storage.db.core.user.UserRepository
import org.springframework.context.ApplicationEventPublisher
Expand Down Expand Up @@ -46,15 +46,15 @@ class CheeringService(
}

@Transactional(readOnly = true)
fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult<Long, CheererResult> {
fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult<CheererResult> {
val cheering = cheeringRepository.findByLifeMapId(lifeMapId)
?: Cheering(0, lifeMapId)

val cheeringSquad = findCheeringSquadWithCursor(lifeMapId, request)

val filteredCheeringSquad = cheeringSquad.transform { CheererResult.from(it) }

return PaginationResult.from(cheering.count, filteredCheeringSquad)
return CursorPaginationResult.from(cheering.count, filteredCheeringSquad)
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -89,7 +89,7 @@ class CheeringService(
)
}

private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult<Long, CheererQueryResult> {
private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult<CheererQueryResult> {
return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) {
id, cursor, size ->
cheererJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package io.raemian.api.goal.controller.request

import java.time.LocalDateTime

data class TimelinePageRequest(
val cursor: LocalDateTime?,
val size: Int,
val page: Int = 0,
val size: Int = 20,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,38 @@ import io.raemian.api.goal.controller.request.TimelinePageRequest
import io.raemian.api.goal.model.GoalTimelineCountSubset
import io.raemian.api.goal.model.GoalTimelinePageResult
import io.raemian.api.lifemap.service.LifeMapService
import io.raemian.api.support.constant.LocalDateTimeConstant
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.OffsetPaginationResult
import io.raemian.api.task.service.TaskService
import io.raemian.storage.db.core.common.pagination.CursorPaginationResult
import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate
import io.raemian.storage.db.core.goal.GoalJdbcQueryRepository
import io.raemian.storage.db.core.goal.model.GoalQueryResult
import io.raemian.storage.db.core.goal.GoalRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Service
class GoalQueryService(
private val lifeMapService: LifeMapService,
private val goalJdbcQueryRepository: GoalJdbcQueryRepository,
private val goalRepository: GoalRepository,
private val emojiService: EmojiService,
private val taskService: TaskService,
private val commentService: CommentService,
) {
@Transactional(readOnly = true)
fun findAllByUsernameWithCursor(username: String, request: TimelinePageRequest): PaginationResult<LocalDateTime, GoalTimelinePageResult> {
fun findAllByUsernameWithOffset(username: String, request: TimelinePageRequest): OffsetPaginationResult<GoalTimelinePageResult> {
val lifeMap = lifeMapService.getFirstByUserName(username)
val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request)
val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page, request.size)
val total = goalRepository.countByLifeMapId(lifeMap.lifeMapId)

val goalIds = goals.contents.map { it.goalId }
val goalIds = goals.map { it.goalId }

val goalCountMap = findGoalCountMap(goalIds)
val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, lifeMap.user.id)

return PaginationResult.from(
lifeMap.goals.size,
goals.transform {
return OffsetPaginationResult.of(
request.page,
request.size,
total,
goals.map {
GoalTimelinePageResult.from(
goal = it,
counts = goalCountMap[it.goalId],
Expand All @@ -58,11 +58,4 @@ class GoalQueryService(
)
}
}

private fun findAllByLifeMapIdWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult<LocalDateTime, GoalQueryResult> {
return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: LocalDateTimeConstant.MAX, request.size) {
id, cursor, size ->
goalJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.raemian.api.lifemap.controller

import io.raemian.api.auth.model.CurrentUser
import io.raemian.api.cheer.service.CheeringService
import io.raemian.api.goal.service.GoalQueryService
import io.raemian.api.lifemap.controller.request.UpdatePublicRequest
import io.raemian.api.lifemap.model.LifeMapResponse
import io.raemian.api.lifemap.service.LifeMapService
Expand All @@ -21,7 +20,6 @@ import org.springframework.web.bind.annotation.RestController
class LifeMapController(
private val lifeMapService: LifeMapService,
private val cheeringService: CheeringService,
private val goalQueryService: GoalQueryService,
) {

@Operation(summary = "๋กœ๊ทธ์ธํ•œ ์œ ์ €์˜ ์ธ์ƒ ์ง€๋„ ์กฐํšŒ API")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import io.raemian.api.goal.service.GoalQueryService
import io.raemian.api.lifemap.model.LifeMapResponse
import io.raemian.api.lifemap.service.LifeMapService
import io.raemian.api.support.response.ApiResponse
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.OffsetPaginationResult
import io.swagger.v3.oas.annotations.Operation
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime

@RestController
@RequestMapping("/open/life-map")
Expand Down Expand Up @@ -51,8 +50,8 @@ class OpenLifeMapController(
fun getTimeline(
@PathVariable("username") username: String,
request: TimelinePageRequest,
): ResponseEntity<ApiResponse<PaginationResult<LocalDateTime, GoalTimelinePageResult>>> {
val goalTimeline = goalQueryService.findAllByUsernameWithCursor(username, request)
): ResponseEntity<ApiResponse<OffsetPaginationResult<GoalTimelinePageResult>>> {
val goalTimeline = goalQueryService.findAllByUsernameWithOffset(username, request)
return ResponseEntity.ok(ApiResponse.success(goalTimeline))
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package io.raemian.api.support.response

import io.raemian.storage.db.core.common.pagination.CursorPaginationResult
import io.raemian.storage.db.core.common.pagination.PaginationResult

data class PaginationResult<CursorType, T>(
data class CursorPaginationResult<T>(
val total: Long,
val contents: List<T>,
val isLast: Boolean,
val nextCursor: CursorType?,
val nextCursor: Long?,
) {
companion object {
fun <CursorType, T> from(total: Long, result: CursorPaginationResult<CursorType, T>): PaginationResult<CursorType, T> {
return PaginationResult(
fun <T> from(total: Long, result: PaginationResult<T>): CursorPaginationResult<T> {
return CursorPaginationResult(
total = total,
contents = result.contents,
isLast = result.isLast,
nextCursor = result.nextCursor,
)
}

fun <CursorType, T> from(total: Int, result: CursorPaginationResult<CursorType, T>): PaginationResult<CursorType, T> {
return PaginationResult(
fun <T> from(total: Int, result: PaginationResult<T>): CursorPaginationResult<T> {
return CursorPaginationResult(
total = total.toLong(),
contents = result.contents,
isLast = result.isLast,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.raemian.api.support.response

data class OffsetPaginationResult<T>(
val total: Long,
val page: Int,
val size: Int,
val contents: List<T>,
) {
companion object {
fun <T> of(page: Int, size: Int, total: Long, contents: List<T>): OffsetPaginationResult<T> {
return OffsetPaginationResult(total, page, size, contents)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ data class CheererQueryResult(
val userNickName: String?,
val userImageUrl: String?,
val cheeringAt: LocalDateTime,
) : CursorExtractable<Long> {
) : CursorExtractable {
override fun cursorId(): Long = cheererId
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package io.raemian.storage.db.core.common.pagination

interface CursorExtractable<CursorType> {
fun cursorId(): CursorType
interface CursorExtractable {
fun cursorId(): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ package io.raemian.storage.db.core.common.pagination
import kotlin.math.min

object CursorPaginationTemplate {
fun <CursorType, T : CursorExtractable<CursorType>> execute(
fun <T : CursorExtractable> execute(
id: Long,
cursorId: CursorType,
cursorId: Long,
size: Int,
query: TriFunction<Long, CursorType, Int, List<T>>,
): CursorPaginationResult<CursorType, T> {
query: TriFunction<Long, Long, Int, List<T>>,
): PaginationResult<T> {
val data = query.apply(id, cursorId, size + 1)
val isLast = data.size != size + 1
val nextCursor = if (isLast) null else data[size].cursorId()

return CursorPaginationResult(
return PaginationResult(
data.subList(0, min(data.size, size)),
nextCursor,
isLast,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package io.raemian.storage.db.core.common.pagination

import java.util.function.Function

data class CursorPaginationResult<CursorType, T> internal constructor(
data class PaginationResult<T> internal constructor(
val contents: List<T>,
val nextCursor: CursorType?,
val nextCursor: Long?,
val isLast: Boolean,
) {
fun <R> transform(transformer: Function<T, R>): CursorPaginationResult<CursorType, R> {
return CursorPaginationResult(
fun <R> transform(transformer: Function<T, R>): PaginationResult<R> {
return PaginationResult(
contents.stream().map(transformer).toList(),
nextCursor,
isLast,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.time.LocalDateTime
class GoalJdbcQueryRepository(
private val jdbcTemplate: NamedParameterJdbcTemplate,
) {
fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: LocalDateTime, size: Int): List<GoalQueryResult> {
fun findAllByLifeMapWithOffset(lifeMapId: Long, page: Int, size: Int): List<GoalQueryResult> {
val sql =
"""
SELECT
Expand All @@ -26,14 +26,14 @@ class GoalJdbcQueryRepository(
LEFT OUTER JOIN stickers s ON g.STICKER_ID = s.ID
WHERE 1 = 1
AND g.LIFE_MAP_ID = :lifeMapId
AND g.DEADLINE <= :cursor
ORDER BY g.DEADLINE DESC
LIMIT :size
OFFSET :page
""".trimIndent()

val namedParameter = MapSqlParameterSource()
.addValue("lifeMapId", lifeMapId)
.addValue("cursor", cursor)
.addValue("page", page)
.addValue("size", size)

return jdbcTemplate.query(sql, namedParameter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import org.springframework.data.repository.query.Param
import java.time.LocalDateTime

interface GoalRepository : JpaRepository<Goal, Long> {

fun countByLifeMapId(lifeMapId: Long): Long

fun findUserByCreatedAtGreaterThanEqual(createdAt: LocalDateTime): List<Goal>

override fun getById(id: Long): Goal =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.raemian.storage.db.core.goal.model

import io.raemian.storage.db.core.common.pagination.CursorExtractable
import java.time.LocalDateTime

data class GoalQueryResult(
Expand All @@ -11,6 +10,4 @@ data class GoalQueryResult(
val stickerUrl: String,
val tag: String,
val createdAt: LocalDateTime,
) : CursorExtractable<LocalDateTime> {
override fun cursorId(): LocalDateTime = deadline
}
)

0 comments on commit ca23a69

Please sign in to comment.