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

[Refactor/#358] 아티클 읽음 기록 이벤트 처리로 리펙토링 #359

Merged
merged 5 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EventListener에서 받은 이벤트를 2차로 처리합니다.

eg)
articleViewHisAsyncHandler.addArticleViewHis는 이벤트를 비동기로 처리함

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 처리하는게 추후 이벤트를 처리하는 로직이 추가될 때 대응하기 편할 것 같아서 이벤트 처리를 현재 구현과 같이 통일하려 합니다.

}
Comment on lines +13 to +16
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1차적으로 EventListener에서 이벤트를 받습니다.

}
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,12 +47,15 @@ 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"
)
)
)

/**
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
Loading