Skip to content

Commit

Permalink
Merge branch 'main' into feat/#239_belljun3395
Browse files Browse the repository at this point in the history
  • Loading branch information
belljun3395 authored Jul 28, 2024
2 parents 8009174 + b319fd0 commit d908e4a
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class LocalCacheConfig {
companion object {
const val LOCAL_CM = "localCacheManager"
const val SELECT_ARTICLE_RECORD_CACHE = "selectArticleRecordCache"
const val SELECT_WORKBOOK_RECORD_CACHE = "selectWorkBookRecordCache"
const val SELECT_WRITER_CACHE = "selectWritersCache"
}

@Bean(LOCAL_CM)
Expand All @@ -38,19 +40,36 @@ class LocalCacheConfig {
)
val cacheManager = EhcacheCachingProvider().cacheManager

val cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(
val cache10Configuration = CacheConfigurationBuilder.newCacheConfigurationBuilder(
Any::class.java,
Any::class.java,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(50, EntryUnit.ENTRIES)
.heap(10, EntryUnit.ENTRIES)
)
.withService(cacheEventListenerConfigurationConfig)
.build()

val cache5Configuration = CacheConfigurationBuilder.newCacheConfigurationBuilder(
Any::class.java,
Any::class.java,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(5, EntryUnit.ENTRIES)
)
.withService(cacheEventListenerConfigurationConfig)
.build()

val selectArticleRecordCacheConfig: javax.cache.configuration.Configuration<Any, Any> =
Eh107Configuration.fromEhcacheCacheConfiguration(cacheConfigurationBuilder)
Eh107Configuration.fromEhcacheCacheConfiguration(cache10Configuration)
val selectWorkBookRecordCacheConfig: javax.cache.configuration.Configuration<Any, Any> =
Eh107Configuration.fromEhcacheCacheConfiguration(cache10Configuration)

val selectWriterCacheConfig: javax.cache.configuration.Configuration<Any, Any> =
Eh107Configuration.fromEhcacheCacheConfiguration(cache5Configuration)

runCatching {
cacheManager.createCache(SELECT_ARTICLE_RECORD_CACHE, selectArticleRecordCacheConfig)
cacheManager.createCache(SELECT_WORKBOOK_RECORD_CACHE, selectWorkBookRecordCacheConfig)
cacheManager.createCache(SELECT_WRITER_CACHE, selectWriterCacheConfig)
}.onFailure {
log.error(it) { "Failed to create cache" }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.few.api.repo.dao.member

import com.few.api.repo.config.LocalCacheConfig.Companion.SELECT_WRITER_CACHE
import com.few.api.repo.dao.member.record.WriterRecord
import org.springframework.cache.CacheManager
import org.springframework.stereotype.Service
import javax.cache.Cache

@Suppress("UNCHECKED_CAST")
@Service
class MemberCacheManager(
private val cacheManager: CacheManager,
) {

private var selectWriterCache: Cache<Any, Any> = cacheManager.getCache(SELECT_WRITER_CACHE)?.nativeCache as Cache<Any, Any>

fun getAllWriterValues(): List<WriterRecord> {
val values = mutableListOf<WriterRecord>()
selectWriterCache.iterator().forEach {
values.add(it.value as WriterRecord)
}
return values
}

fun getAllWriterValues(keys: List<Long>): List<WriterRecord> {
val values = mutableListOf<WriterRecord>()
keys.forEach {
selectWriterCache.get(it)?.let { value ->
values.add(value as WriterRecord)
}
}
return values
}

fun addSelectWorkBookCache(records: List<WriterRecord>) {
records.forEach {
selectWriterCache.put(it.writerId, it)
}
}
}
29 changes: 25 additions & 4 deletions api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.few.api.repo.dao.member

import com.few.api.repo.config.LocalCacheConfig.Companion.LOCAL_CM
import com.few.api.repo.config.LocalCacheConfig.Companion.SELECT_WRITER_CACHE
import com.few.api.repo.dao.member.command.InsertMemberCommand
import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery
import com.few.api.repo.dao.member.query.SelectWriterQuery
Expand All @@ -10,13 +12,16 @@ import com.few.data.common.code.MemberType
import jooq.jooq_dsl.tables.Member
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Repository

@Repository
class MemberDao(
private val dslContext: DSLContext,
private val cacheManager: MemberCacheManager,
) {

@Cacheable(key = "#query.writerId", cacheManager = LOCAL_CM, cacheNames = [SELECT_WRITER_CACHE])
fun selectWriter(query: SelectWriterQuery): WriterRecord? {
val writerId = query.writerId

Expand All @@ -32,18 +37,34 @@ class MemberDao(
.fetchOneInto(WriterRecord::class.java)
}

/**
* 작가 목록 조회 쿼리
* query의 writerIds에 해당하는 작가 목록을 조회한다.
* 이때 먼저 cache에 작가 정보가 있는지 확인하고 없는 경우에만 DB에서 조회한다.
* 조회 이후에는 cache에 저장한다.
*/
fun selectWriters(query: SelectWritersQuery): List<WriterRecord> {
return dslContext.select(
val cachedValues = cacheManager.getAllWriterValues().filter { it.writerId in query.writerIds }
val cachedIdS = cachedValues.map { it.writerId }
val notCachedIds = query.writerIds.filter { it !in cachedIdS }

val fetchedValue = dslContext.select(
Member.MEMBER.ID.`as`(WriterRecord::writerId.name),
DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name").`as`(WriterRecord::name.name),
DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name")
.`as`(WriterRecord::name.name),
DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(WriterRecord::url.name)
)
.from(Member.MEMBER)
.where(Member.MEMBER.ID.`in`(query.writerIds))
.where(Member.MEMBER.ID.`in`(notCachedIds))
.and(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code))
.and(Member.MEMBER.DELETED_AT.isNull)
.orderBy(Member.MEMBER.ID.asc())
.fetchInto(WriterRecord::class.java)
.fetchInto(WriterRecord::class.java).let {
cacheManager.addSelectWorkBookCache(it)
return@let it
}

return cachedValues + fetchedValue
}

fun selectMemberByEmail(query: SelectMemberByEmailQuery): MemberIdRecord? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.few.api.repo.dao.workbook

import com.few.api.repo.config.LocalCacheConfig.Companion.SELECT_WORKBOOK_RECORD_CACHE
import com.few.api.repo.dao.workbook.record.SelectWorkBookRecord
import org.springframework.cache.CacheManager
import org.springframework.stereotype.Service
import javax.cache.Cache

@Suppress("UNCHECKED_CAST")
@Service
class WorkBookCacheManager(
private val cacheManager: CacheManager,
) {

private var selectWorkBookCache: Cache<Any, Any> = cacheManager.getCache(SELECT_WORKBOOK_RECORD_CACHE)?.nativeCache as Cache<Any, Any>

fun getAllSelectWorkBookValues(): List<SelectWorkBookRecord> {
val values = mutableListOf<SelectWorkBookRecord>()
selectWorkBookCache.iterator().forEach {
values.add(it.value as SelectWorkBookRecord)
}
return values
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.few.api.repo.dao.workbook

import com.few.api.repo.config.LocalCacheConfig
import com.few.api.repo.config.LocalCacheConfig.Companion.LOCAL_CM
import com.few.api.repo.dao.workbook.command.InsertWorkBookCommand
import com.few.api.repo.dao.workbook.command.MapWorkBookToArticleCommand
import com.few.api.repo.dao.workbook.query.SelectWorkBookRecordQuery
Expand All @@ -8,12 +10,14 @@ import com.few.data.common.code.CategoryType
import jooq.jooq_dsl.tables.MappingWorkbookArticle
import jooq.jooq_dsl.tables.Workbook
import org.jooq.DSLContext
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Repository

@Repository
class WorkbookDao(
private val dslContext: DSLContext,
) {
@Cacheable(key = "#query.id", cacheManager = LOCAL_CM, cacheNames = [LocalCacheConfig.SELECT_WORKBOOK_RECORD_CACHE])
fun selectWorkBook(query: SelectWorkBookRecordQuery): SelectWorkBookRecord? {
return dslContext.select(
Workbook.WORKBOOK.ID.`as`(SelectWorkBookRecord::id.name),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.few.api.domain.article.usecase

import com.few.api.domain.article.usecase.dto.ReadArticlesUseCaseIn
import com.few.api.domain.article.usecase.dto.ReadArticlesUseCaseOut
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class ReadArticlesUseCase {

@Transactional(readOnly = true)
fun execute(useCaseIn: ReadArticlesUseCaseIn): ReadArticlesUseCaseOut {
return ReadArticlesUseCaseOut(emptyList()) // TODO: impl
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ data class ReadArticleUseCaseOut(
val category: String,
val createdAt: LocalDateTime,
val views: Long,
val includedWorkbooks: List<WorkbookDetail> = emptyList(),
)

data class WriterDetail(
val id: Long,
val name: String,
val url: URL,
)

data class WorkbookDetail(
val id: Long,
val title: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.domain.article.usecase.dto

data class ReadArticlesUseCaseIn(
val prevArticleId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.domain.article.usecase.dto

data class ReadArticlesUseCaseOut(
val articles: List<ReadArticleUseCaseOut>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@ package com.few.api.web.controller.article

import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn
import com.few.api.domain.article.usecase.ReadArticleUseCase
import com.few.api.domain.article.usecase.ReadArticlesUseCase
import com.few.api.domain.article.usecase.dto.ReadArticlesUseCaseIn
import com.few.api.web.controller.article.response.ReadArticleResponse
import com.few.api.web.controller.article.response.ReadArticlesResponse
import com.few.api.web.controller.article.response.WorkbookInfo
import com.few.api.web.controller.article.response.WriterInfo
import com.few.api.web.support.ApiResponse
import com.few.api.web.support.ApiResponseGenerator
import jakarta.validation.constraints.Min
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.validation.annotation.Validated
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 org.springframework.web.bind.annotation.*

@Validated
@RestController
@RequestMapping(value = ["/api/v1/articles"], produces = [MediaType.APPLICATION_JSON_VALUE])
class ArticleController(
private val readArticleUseCase: ReadArticleUseCase,
private val readArticlesUseCase: ReadArticlesUseCase,
) {

@GetMapping("/{articleId}")
Expand All @@ -31,8 +34,58 @@ class ArticleController(
readArticleUseCase.execute(useCaseIn)
}

return ReadArticleResponse(useCaseOut).let {
ApiResponseGenerator.success(it, HttpStatus.OK)
}
val response = ReadArticleResponse(
id = useCaseOut.id,
title = useCaseOut.title,
writer = WriterInfo(
useCaseOut.writer.id,
useCaseOut.writer.name,
useCaseOut.writer.url
),
content = useCaseOut.content,
problemIds = useCaseOut.problemIds,
category = useCaseOut.category,
createdAt = useCaseOut.createdAt,
views = useCaseOut.views
)

return ApiResponseGenerator.success(response, HttpStatus.OK)
}

@GetMapping
fun readArticles(
@RequestParam(
required = false,
defaultValue = "0"
) prevArticleId: Long,
): ApiResponse<ApiResponse.SuccessBody<ReadArticlesResponse>> {
val useCaseOut = readArticlesUseCase.execute(ReadArticlesUseCaseIn(prevArticleId))

val articles: List<ReadArticleResponse> = useCaseOut.articles.map { a ->
ReadArticleResponse(
id = a.id,
title = a.title,
writer = WriterInfo(
a.writer.id,
a.writer.name,
a.writer.url
),
content = a.content,
problemIds = a.problemIds,
category = a.category,
createdAt = a.createdAt,
views = a.views,
includedWorkbooks = a.includedWorkbooks.map { w ->
WorkbookInfo(
id = w.id,
title = w.title
)
}
)
}.toList()

val response = ReadArticlesResponse(articles, articles.size != 10) // TODO refactor 'isLast'

return ApiResponseGenerator.success(response, HttpStatus.OK)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.few.api.web.controller.article.response

import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseOut
import java.net.URL
import java.time.LocalDateTime

Expand All @@ -13,27 +12,16 @@ data class ReadArticleResponse(
val category: String,
val createdAt: LocalDateTime,
val views: Long,
) {
constructor(
useCaseOut: ReadArticleUseCaseOut,
) : this(
id = useCaseOut.id,
writer = WriterInfo(
id = useCaseOut.writer.id,
name = useCaseOut.writer.name,
url = useCaseOut.writer.url
),
title = useCaseOut.title,
content = useCaseOut.content,
problemIds = useCaseOut.problemIds,
category = useCaseOut.category,
createdAt = useCaseOut.createdAt,
views = useCaseOut.views
)
}
val includedWorkbooks: List<WorkbookInfo> = emptyList(),
)

data class WriterInfo(
val id: Long,
val name: String,
val url: URL,
)

data class WorkbookInfo(
val id: Long,
val title: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.web.controller.article.response

data class ReadArticlesResponse(
val articles: List<ReadArticleResponse>,
val isLast: Boolean,
)
Loading

0 comments on commit d908e4a

Please sign in to comment.