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

[Feat/#215] article_view_his 테이블 추가 및 조회수 응답 추가 #216

Merged
merged 20 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3c02659
feat: flyway V1.00.0.7 배포(ARTICLE_VIEW_HIS 테이블 추가)
hun-ca Jul 17, 2024
977de9a
feat: article view history 저장 로직 추가
hun-ca Jul 17, 2024
1334ccf
feat: article 조회시 views 필드 추가
hun-ca Jul 17, 2024
390b898
test: article 조회수 추가 관련 test 코드 수정
hun-ca Jul 17, 2024
2304654
feat: ARTICLE_VIEW_HIS 테이블 인덱스 추가(article_mst_id 컬럼)
hun-ca Jul 17, 2024
29ca0fb
fix: 인덱스 테이블 명 수정
hun-ca Jul 17, 2024
bfaebe6
refactor: DAO 메소드 명 수정
hun-ca Jul 18, 2024
362999e
refactor: 아티클 뷰와 아티클 도메인 통합
hun-ca Jul 18, 2024
9c17140
refactor: DAO 빈 지정 stereotype 변경(@Component -> @Repository)
hun-ca Jul 18, 2024
abde0c3
refactor: 조회수 결정에 대한 로직을 DAO -> UC로 이동
hun-ca Jul 18, 2024
89a188f
chore: member Id 사용에 대한 TODO 주석 추가
hun-ca Jul 18, 2024
a8342ba
feat: 아티클 조회 기록 Insert과정을 다른 쓰레드에서 수행하도록 변경
hun-ca Jul 18, 2024
21c0ad4
fix: add ArticleViewHisAsyncEvent
hun-ca Jul 18, 2024
4ce6ac9
test: ReadArticleUseCaseTest 수정반영
hun-ca Jul 18, 2024
51671e4
refactor: SubscribeWorkbookUseCaseTest 에서 print 삭제 및 log 적용
hun-ca Jul 18, 2024
a76ed19
refactor: rename ArticleViewHisAsyncHandler
hun-ca Jul 18, 2024
dc76e36
test: application-test.yaml에 database async thread pool 설정 추가
hun-ca Jul 18, 2024
7bac2fc
fix: 테스트 통과하지 못하는 문제 해결
belljun3395 Jul 19, 2024
ba64abd
Merge remote-tracking branch 'origin/main' into feat/#215_hunca
hun-ca Jul 19, 2024
3348881
feat: 아티클 조회응답 바디 views 필드 추가
hun-ca Jul 19, 2024
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,10 @@ Footer
*.log
*.tmp

# DB schema migration path (except data module)
data/src/main/resources/**/*.sql
api/**/*.sql
api-repo/**/*.sql
batch/**/*.sql
email/**/*.sql
storage/**/*.sql
Comment on lines +98 to +104
Copy link
Member Author

Choose a reason for hiding this comment

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

각 모듈별로 jooq 필드시 생성되는 .sql 파일이 gitignore되어있었는데, 최상위 위치에 통합관리하도록 변경했습니다

2 changes: 0 additions & 2 deletions api-repo/.gitignore

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.few.api.repo.dao.article

import com.few.api.repo.dao.article.command.ArticleViewHisCommand
import com.few.api.repo.dao.article.query.ArticleViewHisCountQuery
import jooq.jooq_dsl.tables.ArticleViewHis
import org.jooq.DSLContext
import org.springframework.stereotype.Repository

@Repository
Copy link
Member Author

@hun-ca hun-ca Jul 17, 2024

Choose a reason for hiding this comment

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

종준님은 DAO에 @Repository 쓰셨던데 저는 @Component 썼거든요 뭐가 더 어울릴까요? 통일하는게 좋을거 같아서

Copy link
Collaborator

Choose a reason for hiding this comment

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

스크린샷 2024-07-18 오전 12 21 48 스크린샷 2024-07-18 오전 12 21 17 우선 블로그 글이긴 한데 저도 저렇게 알고 있어서 `@Repository`로 하는게 좋지 않을까 생각합니다..!

class ArticleViewHisDao(
private val dslContext: DSLContext,
) {

fun insertArticleViewHis(command: ArticleViewHisCommand) {
dslContext.insertInto(
ArticleViewHis.ARTICLE_VIEW_HIS,
ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID,
ArticleViewHis.ARTICLE_VIEW_HIS.MEMBER_ID
).values(
command.articleId,
command.memberId
).execute()
}
Comment on lines +14 to +23
Copy link
Member Author

Choose a reason for hiding this comment

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

article_view_his 테이블 insert. articleId에 인덱스 추가해야겠다...


fun selectArticleViews(query: ArticleViewHisCountQuery): Long {
Copy link
Collaborator

Choose a reason for hiding this comment

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

스크린샷 2024-07-18 오후 1 11 46

저는 countXXX 형식으로 네이밍을 했는데
저희 요 부분도 통일하기 위한 논의가 필요할 것 같아요!

return dslContext.selectCount()
.from(ArticleViewHis.ARTICLE_VIEW_HIS)
.where(ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID.eq(query.articleId))
.fetchOne(0, Long::class.java) ?: 0L
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기서는 Long? 타입으로 반환하고 ?: 0L 로 처리한 부분을 UC에서 하는게 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

로직은 최대한 UC로 뺴는게 좋겠네요

}
Copy link
Member Author

Choose a reason for hiding this comment

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

아티클 별 view 개수 조회. 이거때문에 article id에 인덱스 해야겠네요 추가커밋 할게요

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.repo.dao.article.command

data class ArticleViewHisCommand(
val articleId: Long,
val memberId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.repo.dao.article.query

data class ArticleViewHisCountQuery(
val articleId: Long,
)
2 changes: 0 additions & 2 deletions api/.gitignore

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.few.api.domain.article.service

import com.few.api.domain.article.service.dto.AddArticleViewHisInDto
import com.few.api.domain.article.service.dto.ReadArticleViewsInDto
import com.few.api.repo.dao.article.ArticleViewHisDao
import com.few.api.repo.dao.article.command.ArticleViewHisCommand
import com.few.api.repo.dao.article.query.ArticleViewHisCountQuery
import org.springframework.stereotype.Service

@Service
class ArticleViewHisService(
private val articleViewHisDao: ArticleViewHisDao,
) {
fun addArticleViewHis(inDto: AddArticleViewHisInDto) {
articleViewHisDao.insertArticleViewHis(
ArticleViewHisCommand(inDto.articleId, inDto.memberId)
)
}

fun readArticleViews(inDto: ReadArticleViewsInDto): Long {
return articleViewHisDao.selectArticleViews(
ArticleViewHisCountQuery(inDto.articleId)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.domain.article.service.dto

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

data class ReadArticleViewsInDto(
val articleId: Long,
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.few.api.domain.article.usecase

import com.few.api.domain.article.service.ArticleViewHisService
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
import com.few.api.domain.article.service.BrowseArticleProblemsService
import com.few.api.domain.article.service.ReadArticleWriterRecordService
import com.few.api.domain.article.service.dto.AddArticleViewHisInDto
import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsInDto
import com.few.api.domain.article.service.dto.ReadArticleViewsInDto
import com.few.api.domain.article.service.dto.ReadWriterRecordInDto
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.article.ArticleDao
Expand All @@ -19,6 +22,7 @@ class ReadArticleUseCase(
private val articleDao: ArticleDao,
private val readArticleWriterRecordService: ReadArticleWriterRecordService,
private val browseArticleProblemsService: BrowseArticleProblemsService,
private val articleViewHisService: ArticleViewHisService,
Copy link
Collaborator

Choose a reason for hiding this comment

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

articleView 도 article 도메인에 포함된다고 생각해서 저는 ArticleViewDao를 바로 사용해도 괜찮지 않을까 생각하는데 어떻게 생각하시는지 궁금합니다

) {

@Transactional(readOnly = true)
Expand All @@ -35,6 +39,9 @@ class ReadArticleUseCase(
browseArticleProblemsService.execute(query)
}

articleViewHisService.addArticleViewHis(AddArticleViewHisInDto(useCaseIn.articleId, useCaseIn.memberId))
val views = articleViewHisService.readArticleViews(ReadArticleViewsInDto(useCaseIn.articleId))
Copy link
Collaborator

Choose a reason for hiding this comment

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

요거는 아이디어 차원의 이야기인데 아래 방법은 어떨지 궁금합니다.

기존:

  1. addArticleViewHis로 뷰 확인 기록을 추가한다.
  2. readArticleViews로 뷰 확인 기록을 조회한다.

제안:

  1. readArticleViews로 뷰 확인 기록을 조회한다.
  2. 조회한 값에 +1 하여 views를 반환한다.
  3. addArticleViewHis로 뷰 확인 기록을 추가는 별도의 스레드에서 비동기로 진행한다.

Copy link
Member Author

Choose a reason for hiding this comment

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

제안한 방식 나도 생각했던건데.. Insert는 테이블 락 + 인덱스 재구조화 때문에 항상 신중해야하는데, 안그래도 엄청 자주 호출될 SQL이라... 그게 좋은거 같아요


return ReadArticleUseCaseOut(
id = articleRecord.articleId,
writer = WriterDetail(
Expand All @@ -46,7 +53,8 @@ class ReadArticleUseCase(
content = articleRecord.content,
problemIds = problemIds.problemIds,
category = CategoryType.convertToDisplayName(articleRecord.category),
createdAt = articleRecord.createdAt
createdAt = articleRecord.createdAt,
views = views
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package com.few.api.domain.article.usecase.dto

data class ReadArticleUseCaseIn(
val articleId: Long,
val memberId: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data class ReadArticleUseCaseOut(
val problemIds: List<Long>,
val category: String,
val createdAt: LocalDateTime,
val views: Long,
)

data class WriterDetail(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ArticleController(
@Min(value = 1, message = "{min.id}")
articleId: Long,
): ApiResponse<ApiResponse.SuccessBody<ReadArticleResponse>> {
val useCaseOut = ReadArticleUseCaseIn(articleId).let { useCaseIn: ReadArticleUseCaseIn ->
val useCaseOut = ReadArticleUseCaseIn(articleId = articleId, memberId = 1L).let { useCaseIn: ReadArticleUseCaseIn ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

요거는 관련 처리를 하지 않아서 1L로 한것이죠?
1L은 값이 있어서 0L로 하고 todo로 표시하는건 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

네네 유저관련 기능 개발해주시면서 해당 부분 변경 부탁드립니다! 일단 0으로 해둘게요

readArticleUseCase.execute(useCaseIn)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.few.api.domain.article.usecase

import com.few.api.domain.article.service.ArticleViewHisService
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 @@ -9,9 +10,7 @@ import com.few.api.repo.dao.article.ArticleDao
import com.few.api.repo.dao.article.record.SelectArticleRecord
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.mockk.*

import java.net.URL
import java.time.LocalDateTime
Expand All @@ -22,13 +21,20 @@ class ReadArticleUseCaseTest : BehaviorSpec({
lateinit var readArticleWriterRecordService: ReadArticleWriterRecordService
lateinit var browseArticleProblemsService: BrowseArticleProblemsService
lateinit var useCase: ReadArticleUseCase
val useCaseIn = ReadArticleUseCaseIn(articleId = 1L)
lateinit var articleViewHisService: ArticleViewHisService
val useCaseIn = ReadArticleUseCaseIn(articleId = 1L, memberId = 1L)

beforeContainer {
articleDao = mockk<ArticleDao>()
readArticleWriterRecordService = mockk<ReadArticleWriterRecordService>()
browseArticleProblemsService = mockk<BrowseArticleProblemsService>()
useCase = ReadArticleUseCase(articleDao, readArticleWriterRecordService, browseArticleProblemsService)
articleViewHisService = mockk<ArticleViewHisService>()
useCase = ReadArticleUseCase(
articleDao,
readArticleWriterRecordService,
browseArticleProblemsService,
articleViewHisService
)
}

given("아티클 조회 요청이 온 상황에서") {
Expand All @@ -52,6 +58,8 @@ class ReadArticleUseCaseTest : BehaviorSpec({
every { articleDao.selectArticleRecord(any()) } returns record
every { readArticleWriterRecordService.execute(any()) } returns writerSvcOutDto
every { browseArticleProblemsService.execute(any()) } returns probSvcOutDto
every { articleViewHisService.addArticleViewHis(any()) } just Runs
every { articleViewHisService.readArticleViews(any()) } returns 1L

then("아티클이 정상 조회된다") {
useCase.execute(useCaseIn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ class ArticleControllerTest : ControllerTestSpec() {
val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/{articleId}").build().toUriString()
// set usecase mock
val articleId = 1L
`when`(readArticleUseCase.execute(ReadArticleUseCaseIn(articleId))).thenReturn(
val memberId = 1L
`when`(readArticleUseCase.execute(ReadArticleUseCaseIn(articleId, memberId))).thenReturn(
ReadArticleUseCaseOut(
id = 1L,
writer = WriterDetail(
Expand All @@ -81,7 +82,8 @@ class ArticleControllerTest : ControllerTestSpec() {
content = CategoryType.fromCode(0)!!.name,
problemIds = listOf(1L, 2L, 3L),
category = "경제",
createdAt = LocalDateTime.now()
createdAt = LocalDateTime.now(),
views = 1L
)
)

Expand Down Expand Up @@ -119,7 +121,9 @@ class ArticleControllerTest : ControllerTestSpec() {
PayloadDocumentation.fieldWithPath("data.category")
.fieldWithString("아티클 카테고리"),
PayloadDocumentation.fieldWithPath("data.createdAt")
.fieldWithString("아티클 생성일")
.fieldWithString("아티클 생성일"),
PayloadDocumentation.fieldWithPath("data.views")
.fieldWithString("아티클 조회수")
)
)
).build()
Expand Down
2 changes: 0 additions & 2 deletions batch/.gitignore

This file was deleted.

2 changes: 0 additions & 2 deletions data/.gitignore

This file was deleted.

13 changes: 13 additions & 0 deletions data/db/migration/entity/V1.00.0.7__article_view_his_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- 아티클 조회수 저장 테이블
CREATE TABLE ARTICLE_VIEW_HIS
(
id BIGINT NOT NULL AUTO_INCREMENT,
article_mst_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (id)
);
Comment on lines +2 to +10
Copy link
Member Author

Choose a reason for hiding this comment

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

ARTICLE_VIEW_HIS 테이블 스키마


-- [인덱스 추가] --
CREATE INDEX article_view_his_idx1 ON ARTICLE_VIEW_HIS (article_mst_id);
2 changes: 0 additions & 2 deletions email/.gitignore

This file was deleted.

2 changes: 0 additions & 2 deletions storage/.gitignore

This file was deleted.

Loading