Skip to content

Commit

Permalink
[Refactor/#358] 아티클 읽음 기록 이벤트 처리로 리펙토링 (#359)
Browse files Browse the repository at this point in the history
* feat: ReadArticleEvent 생성 및 처리 구현

* refactor: 기존 구현 이벤트 방식으로 수정

* chore: ReadArticlesUseCase -> BrowseArticlesUseCase로 컨벤션 통일

* chore: 불필요 주석 제거
  • Loading branch information
belljun3395 authored Aug 27, 2024
1 parent eaa3ff5 commit 93eb1b8
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.few.api.domain.article.event

import com.few.api.domain.article.event.dto.ReadArticleEvent
import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component

@Component
class ReadArticleEventListener(
private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler,
) {

@EventListener
fun handleEvent(event: ReadArticleEvent) {
articleViewHisAsyncHandler.addArticleViewHis(event.articleId, event.memberId, event.category)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.few.api.domain.article.event.dto

import com.few.data.common.code.CategoryType

data class ReadArticleEvent(
val articleId: Long,
val memberId: Long,
val category: CategoryType,
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.util.*
import kotlin.Comparator

@Component
class ReadArticlesUseCase(
class BrowseArticlesUseCase(
private val articleViewCountDao: ArticleViewCountDao,
private val articleMainCardDao: ArticleMainCardDao,
private val articleDao: ArticleDao,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.few.api.domain.article.usecase

import com.few.api.domain.article.event.dto.ReadArticleEvent
import com.few.api.domain.article.handler.ArticleViewCountHandler
import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler
import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn
import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseOut
import com.few.api.domain.article.usecase.dto.WriterDetail
Expand All @@ -13,6 +13,7 @@ import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.article.ArticleDao
import com.few.api.repo.dao.article.query.SelectArticleRecordQuery
import com.few.data.common.code.CategoryType
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

Expand All @@ -21,8 +22,8 @@ class ReadArticleUseCase(
private val articleDao: ArticleDao,
private val readArticleWriterRecordService: ReadArticleWriterRecordService,
private val browseArticleProblemsService: BrowseArticleProblemsService,
private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler,
private val articleViewCountHandler: ArticleViewCountHandler,
private val applicationEventPublisher: ApplicationEventPublisher,
) {

@Transactional(readOnly = true)
Expand All @@ -40,20 +41,17 @@ class ReadArticleUseCase(
browseArticleProblemsService.execute(query)
}

// ARTICLE VIEW HIS에 저장하기 전에 먼저 VIEW COUNT 조회하는 순서 변경 금지
val views = articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId)
articleViewHisAsyncHandler.addArticleViewHis(
useCaseIn.articleId,
useCaseIn.memberId,
CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException("article.invalid.category")
applicationEventPublisher.publishEvent(
ReadArticleEvent(
articleId = useCaseIn.articleId,
memberId = useCaseIn.memberId,
category = CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException(
"article.invalid.category"
)
)
)

/**
* NOTE: The articleViewHisAsyncHandler creates a new transaction that is separate from the current context.
* So this section, the logic after the articleViewHisAsyncHandler call,
* is where the mismatch between the two transactions can occur if an exception is thrown.
*/

return ReadArticleUseCaseOut(
id = articleRecord.articleId,
writer = WriterDetail(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.few.api.domain.workbook.article.usecase

import com.few.api.domain.article.event.dto.ReadArticleEvent
import com.few.api.domain.article.handler.ArticleViewCountHandler
import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler
import com.few.api.domain.article.service.BrowseArticleProblemsService
import com.few.api.domain.article.service.ReadArticleWriterRecordService
import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsInDto
Expand All @@ -13,6 +13,7 @@ import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.article.ArticleDao
import com.few.api.repo.dao.article.query.SelectWorkBookArticleRecordQuery
import com.few.data.common.code.CategoryType
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand All @@ -21,8 +22,8 @@ class ReadWorkBookArticleUseCase(
private val articleDao: ArticleDao,
private val readArticleWriterRecordService: ReadArticleWriterRecordService,
private val browseArticleProblemsService: BrowseArticleProblemsService,
private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler,
private val articleViewCountHandler: ArticleViewCountHandler,
private val applicationEventPublisher: ApplicationEventPublisher,
) {
@Transactional(readOnly = true)
fun execute(useCaseIn: ReadWorkBookArticleUseCaseIn): ReadWorkBookArticleOut {
Expand All @@ -46,20 +47,17 @@ class ReadWorkBookArticleUseCase(
/**
* @see com.few.api.domain.article.usecase.ReadArticleUseCase
*/
// ARTICLE VIEW HIS에 저장하기 전에 먼저 VIEW COUNT 조회하는 순서 변경 금지
val views = articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId)
articleViewHisAsyncHandler.addArticleViewHis(
useCaseIn.articleId,
useCaseIn.memberId,
CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException("article.invalid.category")
articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId)
applicationEventPublisher.publishEvent(
ReadArticleEvent(
articleId = useCaseIn.articleId,
memberId = useCaseIn.memberId,
category = CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException(
"article.invalid.category"
)
)
)

/**
* NOTE: The articleViewHisAsyncHandler creates a new transaction that is separate from the current context.
* So this section, the logic after the articleViewHisAsyncHandler call,
* is where the mismatch between the two transactions can occur if an exception is thrown.
*/

return ReadWorkBookArticleOut(
id = articleRecord.articleId,
writer = WriterDetail(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.few.api.web.controller.article

import com.few.api.domain.article.usecase.ReadArticleUseCase
import com.few.api.domain.article.usecase.ReadArticlesUseCase
import com.few.api.domain.article.usecase.BrowseArticlesUseCase
import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn
import com.few.api.domain.article.usecase.dto.ReadArticlesUseCaseIn
import com.few.api.security.filter.token.AccessTokenResolver
Expand All @@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.*
@RequestMapping(value = ["/api/v1/articles"], produces = [MediaType.APPLICATION_JSON_VALUE])
class ArticleController(
private val readArticleUseCase: ReadArticleUseCase,
private val readArticlesUseCase: ReadArticlesUseCase,
private val browseArticlesUseCase: BrowseArticlesUseCase,
private val tokenResolver: TokenResolver,
) {

Expand Down Expand Up @@ -81,7 +81,7 @@ class ArticleController(
defaultValue = "-1"
) categoryCd: Byte,
): ApiResponse<ApiResponse.SuccessBody<ReadArticlesResponse>> {
val useCaseOut = readArticlesUseCase.execute(ReadArticlesUseCaseIn(prevArticleId, categoryCd))
val useCaseOut = browseArticlesUseCase.execute(ReadArticlesUseCaseIn(prevArticleId, categoryCd))

val articles: List<ReadArticleResponse> = useCaseOut.articles.map { a ->
ReadArticleResponse(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.few.api.domain.article.usecase

import com.few.api.domain.article.event.dto.ReadArticleEvent
import com.few.api.domain.article.handler.ArticleViewCountHandler
import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler
import com.few.api.domain.article.service.BrowseArticleProblemsService
import com.few.api.domain.article.service.ReadArticleWriterRecordService
import com.few.api.domain.article.service.dto.BrowseArticleProblemsOutDto
Expand All @@ -15,6 +15,7 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
import org.springframework.context.ApplicationEventPublisher

import java.net.URL
import java.time.LocalDateTime
Expand All @@ -26,21 +27,22 @@ class ReadArticleUseCaseTest : BehaviorSpec({
lateinit var readArticleWriterRecordService: ReadArticleWriterRecordService
lateinit var browseArticleProblemsService: BrowseArticleProblemsService
lateinit var useCase: ReadArticleUseCase
lateinit var articleViewHisAsyncHandler: ArticleViewHisAsyncHandler
lateinit var articleViewCountHandler: ArticleViewCountHandler
lateinit var applicationEventPublisher: ApplicationEventPublisher

beforeContainer {
articleDao = mockk<ArticleDao>()
readArticleWriterRecordService = mockk<ReadArticleWriterRecordService>()
browseArticleProblemsService = mockk<BrowseArticleProblemsService>()
articleViewHisAsyncHandler = mockk<ArticleViewHisAsyncHandler>()
articleViewCountHandler = mockk<ArticleViewCountHandler>()
applicationEventPublisher = mockk<ApplicationEventPublisher>()

useCase = ReadArticleUseCase(
articleDao,
readArticleWriterRecordService,
browseArticleProblemsService,
articleViewHisAsyncHandler,
articleViewCountHandler
articleViewCountHandler,
applicationEventPublisher
)
}

Expand Down Expand Up @@ -80,9 +82,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({
val views = 1L
every { articleViewCountHandler.browseArticleViewCount(any()) } returns views

every { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) } answers {
log.debug { "Inserting article view history asynchronously" }
}
every { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } just Runs

then("아티클과 연관된 정보를 조회한다") {
val useCaseOut = useCase.execute(useCaseIn)
Expand All @@ -103,7 +103,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({
verify(exactly = 1) { readArticleWriterRecordService.execute(any()) }
verify(exactly = 1) { browseArticleProblemsService.execute(any()) }
verify(exactly = 1) { articleViewCountHandler.browseArticleViewCount(any()) }
verify(exactly = 1) { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) }
verify(exactly = 1) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) }
}
}

Expand All @@ -117,7 +117,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({
verify(exactly = 0) { readArticleWriterRecordService.execute(any()) }
verify(exactly = 0) { browseArticleProblemsService.execute(any()) }
verify(exactly = 0) { articleViewCountHandler.browseArticleViewCount(any()) }
verify(exactly = 0) { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) }
verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) }
}
}

Expand Down Expand Up @@ -146,7 +146,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({
verify(exactly = 1) { readArticleWriterRecordService.execute(any()) }
verify(exactly = 0) { browseArticleProblemsService.execute(any()) }
verify(exactly = 0) { articleViewCountHandler.browseArticleViewCount(any()) }
verify(exactly = 0) { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) }
verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.few.api.ApiMain
import com.few.api.domain.admin.document.usecase.*
import com.few.api.domain.article.usecase.ReadArticleUseCase
import com.few.api.domain.article.usecase.ReadArticlesUseCase
import com.few.api.domain.article.usecase.BrowseArticlesUseCase
import com.few.api.domain.log.AddApiLogUseCase
import com.few.api.domain.member.usecase.SaveMemberUseCase
import com.few.api.domain.member.usecase.TokenUseCase
Expand Down Expand Up @@ -73,7 +73,7 @@ abstract class ControllerTestSpec {
lateinit var readArticleUseCase: ReadArticleUseCase

@MockBean
lateinit var readArticlesUseCase: ReadArticlesUseCase
lateinit var browseArticlesUseCase: BrowseArticlesUseCase

/** MemberControllerTest */
@MockBean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ class ArticleControllerTest : ControllerTestSpec() {
}.toList(),
true
)
`when`(readArticlesUseCase.execute(useCaseIn)).thenReturn(useCaseOut)
`when`(browseArticlesUseCase.execute(useCaseIn)).thenReturn(useCaseOut)

// when
mockMvc.perform(
Expand Down

0 comments on commit 93eb1b8

Please sign in to comment.