Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V0.1.9 #86

Merged
merged 11 commits into from
Jan 14, 2024
2 changes: 1 addition & 1 deletion .run/EatdaApplication.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
</component>
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ tasks.jacocoTestCoverageVerification {
"*.common.*",
"*.dto.*",
"com.mjucow.eatda.jooq.*",
"*.Companion"
"*.Companion",
"*.popularstore.*" // FIXME: redis 이슈 해결 후 제거'[
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### Application version ###
applicationVersion=0.1.7-RELEASE
applicationVersion=0.1.8-RELEASE

### Project configs ###
projectGroup="com.mjucow"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ class SwaggerConfig {
Info()
.title("Eatda API Documentation")
.description("Eatda(잇다) 서비스의 API 명세서입니다.")
.version("0.1.7")
.version("0.1.8")
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.mjucow.eatda.domain.notice.service.command.dto

import io.swagger.v3.oas.annotations.media.Schema

data class CreateNoticeCommand(
@Schema(name = "title", example = "공지사항 제목")
val title: String,
@Schema(name = "content", example = "공지사항 내용")
val content: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package com.mjucow.eatda.domain.store.service.command.dto
import io.swagger.v3.oas.annotations.media.Schema

data class CreateCommand(
@Schema(description = "생성할 카테고리 이름", example = "validName")
@Schema(name = "name", description = "생성할 카테고리 이름", example = "validName")
val name: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.mjucow.eatda.common.vo.Point
import io.swagger.v3.oas.annotations.media.Schema

data class StoreUpdateCommand(
@Schema(name = "Store Name", example = "명지대학교")
@Schema(name = "name", example = "명지대학교")
val name: String,
@Schema(name = "address", example = "서울특별시 서대문구 거북골로 34")
val address: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mjucow.eatda.domain.store.service.query

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
Expand All @@ -11,22 +10,30 @@ import org.springframework.transaction.annotation.Transactional
@Transactional(readOnly = true)
class StoreQueryService(
val repository: StoreRepository,
val popularStoreCommandService: PopularStoreCommandService,
// val popularStoreCommandService: PopularStoreCommandService,
) {
fun findAllByCategoryAndCursor(id: Long? = null, categoryId: Long? = null, size: Int): List<StoreDto> {
fun findAllByCategoryAndCursor(cursor: Long? = null, categoryId: Long? = null, size: Int): List<StoreDto> {
return if (categoryId == null) {
repository.findAllByIdLessThanOrderByIdDesc(size, id).map(StoreDto::from)
repository.findAllByIdLessThanOrderByIdDesc(
size = size,
id = cursor
).map(StoreDto::from)
} else {
// FIXME(cache): store 캐시 처리 이후 store 조회 개선하기
val storeIds = repository.findIdsByCategoryIdOrderByIdDesc(categoryId, size, id)
val storeIds = repository.findIdsByCategoryIdOrderByIdDesc(
categoryId = categoryId,
size = size,
id = cursor
)
repository.findAllByIdInOrderByIdDesc(storeIds).map(StoreDto::from)
}
}

fun findById(storeId: Long): StoreDetailDto {
val entity = repository.getReferenceById(storeId)
// FIXME(async): findById가 popular store command 과정에서 실패하지 않도록 비동기 호출 처리
popularStoreCommandService.setStore(storeId)
// TODO: Redis 이슈 해결 후 주석 제거
// popularStoreCommandService.setStore(storeId)
return StoreDetailDto.from(entity)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface StoreApiPresentation {
@Operation(summary = "커서 기반 카테고리 가게 조회", description = "커서를 기반으로 가게를 조회합니다.")
@StoreDtosApiResponse
fun findAllByCategoryIdAndCursor(
storeId: Long?,
cursor: Long?,
categoryId: Long?,
pageSize: Int,
): ApiResponse<CursorPage<StoreDto>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ class StoreController(
@GetMapping
@ResponseStatus(HttpStatus.OK)
override fun findAllByCategoryIdAndCursor(
@RequestParam("storeId", required = false) storeId: Long?,
@RequestParam("cursor", required = false) cursor: Long?,
@RequestParam("categoryId", required = false) categoryId: Long?,
@RequestParam("pageSize", required = false, defaultValue = "20") pageSize: Int,
): ApiResponse<CursorPage<StoreDto>> {
val results = storeQueryService.findAllByCategoryAndCursor(storeId, categoryId, pageSize)
val results = storeQueryService.findAllByCategoryAndCursor(cursor, categoryId, pageSize)

val contents = results.subList(0, min(pageSize, results.size))
val hasNext = results.size > pageSize
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,48 @@
package com.mjucow.eatda.presentation.store.popularstore

import com.mjucow.eatda.domain.poplarstore.service.PopularStoreCacheService
import com.mjucow.eatda.domain.poplarstore.service.PopularStoreQueryService
import com.mjucow.eatda.domain.poplarstore.service.dto.PopularStoreDtos
import com.mjucow.eatda.presentation.common.ApiResponse
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import java.time.Instant

@RequestMapping("/api/v1/stores/popular")
@RestController
class PopularStoreController(
private val cacheService: PopularStoreCacheService,
private val queryService: PopularStoreQueryService,
) : PopularStoreApiPresentation {
@GetMapping
@ResponseStatus(HttpStatus.OK)
override fun findAllPopularStore(): ApiResponse<PopularStoreDtos> {
return ApiResponse.success(queryService.getPopularStores())
}

@PostMapping("/cache/{storeId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun setRedis(@PathVariable storeId: String) {
cacheService.setStore(Instant.now(), storeId.toLong())
}

@GetMapping("/cache/{storeId}")
@ResponseStatus(HttpStatus.OK)
fun getRedis(@PathVariable storeId: String): ApiResponse<RedisCache?> {
val key = cacheService.createSearchKey(Instant.now())
val popularStores = cacheService.getStoresSortByPopular(key)
if (popularStores.isEmpty()) {
return ApiResponse.success(null)
} else {
val e = popularStores[0]
return ApiResponse.success(RedisCache(e.storeId, e.count))
}
}

data class RedisCache(val storeId: Long, val count: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
// given
val pageSize = 2
val latestId = IntStream.range(0, pageSize * 2).mapToLong {
repository.save(Store(name = "validName$it", address = StoreMother.ADDRESS)).id
repository.save(
Store(
name = "validName$it",
address = StoreMother.ADDRESS
)
).id
}.max().asLong

// when
Expand All @@ -84,11 +89,19 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
// given
val pageSize = Store.MAX_NAME_LENGTH - 1
repeat(Store.MAX_NAME_LENGTH) {
repository.save(Store(name = "x".repeat(it + 1), address = StoreMother.ADDRESS))
repository.save(
Store(
name = "x".repeat(it + 1),
address = StoreMother.ADDRESS
)
)
}

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

// then
assertThat(result.size).isEqualTo(pageSize + 1)
Expand All @@ -103,7 +116,10 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
val pageSize = 10

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

// then
assertThat(result.size).isLessThan(pageSize)
Expand All @@ -118,7 +134,10 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
repository.save(store)

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

// then
assertThat(result).isEmpty()
Expand All @@ -137,7 +156,10 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
}.max().asLong

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

// then
assertThat(result).anyMatch { it.id == latestId }
Expand Down Expand Up @@ -178,7 +200,7 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
// when
val result = storeQueryService.findAllByCategoryAndCursor(
categoryId = category.id,
id = storeId + 1,
cursor = storeId + 1,
size = pageSize
)

Expand All @@ -198,7 +220,7 @@ class StoreQueryServiceDataTest : AbstractDataTest() {
// when
val result = storeQueryService.findAllByCategoryAndCursor(
categoryId = category.id,
id = store.id,
cursor = store.id,
size = 10
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ class StoreControllerMvcTest : AbstractMockMvcTest() {
// given
val entityId = 1L
every { storeCommandService.create(any()) } returns entityId
val command = StoreCreateCommand(name = StoreMother.NAME, address = StoreMother.ADDRESS)
val command = StoreCreateCommand(
name = StoreMother.NAME,
address = StoreMother.ADDRESS
)
val content = objectMapper.writeValueAsString(command)

// when & then
Expand Down Expand Up @@ -95,7 +98,7 @@ class StoreControllerMvcTest : AbstractMockMvcTest() {
// when & then
mockMvc.perform(
RestDocumentationRequestBuilders.get(
"$BASE_URI?storeId={storeId}&size={size}&categoryId={categoryId}",
"$BASE_URI?cursor={cursor}&size={size}&categoryId={categoryId}",
storeDtos.size + 1,
pageSize,
null
Expand All @@ -114,7 +117,7 @@ class StoreControllerMvcTest : AbstractMockMvcTest() {
.tag("store")
.description("커서 기반 카테고리 가게 조회")
.queryParameters(
ResourceDocumentation.parameterWithName("storeId").description("조회한 마지막 가게 식별자").optional(),
ResourceDocumentation.parameterWithName("cursor").description("조회한 마지막 가게 식별자").optional(),
ResourceDocumentation.parameterWithName("categoryId").description("조회하는 가게의 카테고리의 식별자").optional(),
ResourceDocumentation.parameterWithName("size").description("조회할 페이지 사이즈").optional()
)
Expand Down Expand Up @@ -154,7 +157,7 @@ class StoreControllerMvcTest : AbstractMockMvcTest() {
// when & then
mockMvc.perform(
RestDocumentationRequestBuilders.get(
"$BASE_URI?storeId={storeId}&size={size}&categoryId={categoryId}",
"$BASE_URI?cursor={cursor}&size={size}&categoryId={categoryId}",
storeDtos.size + 1,
pageSize,
1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.mjucow.eatda.presentation.store.popularstore

import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper
import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder
import com.mjucow.eatda.domain.poplarstore.service.PopularStoreCacheService
import com.mjucow.eatda.domain.poplarstore.service.PopularStoreQueryService
import com.mjucow.eatda.domain.poplarstore.service.dto.PopularStoreDtos
import com.mjucow.eatda.presentation.AbstractMockMvcTest
Expand All @@ -18,6 +19,9 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers

@WebMvcTest(PopularStoreController::class)
class PopularStoreControllerTest : AbstractMockMvcTest() {
@MockkBean(relaxUnitFun = true)
lateinit var cacheService: PopularStoreCacheService

@MockkBean(relaxUnitFun = true)
lateinit var popularStoreQueryService: PopularStoreQueryService

Expand Down