Skip to content

Commit

Permalink
[Fix/#401] 하루안에 재구독시 아티클 중복 전송 및 진행률 추가되는 문제 해결 (#402)
Browse files Browse the repository at this point in the history
* feat: 구독 발송 시간 컬럼 추가

* refactor: 아티클 전송 배치에 발송 시간 추가

* refactor: selectSubscriptionTimeRecordQuery 추가 및 전송 후 수정시 modified at, send at 추가

* refactor: SUBSCRIPTION MEMBER ID WORKBOOK ID Case 추가

* refactor: WorkbookSubscriptionAfterCompletionEventListener에 트랜잭션 제거

* refactor: 동일 날짜에 재구독시 아티클 전송하지 않도록 수정
  • Loading branch information
belljun3395 authored Sep 15, 2024
1 parent 8158785 commit 22d9abb
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package com.few.api.repo.dao.subscription

import com.few.api.repo.dao.subscription.command.*
import com.few.api.repo.dao.subscription.query.*
import com.few.api.repo.dao.subscription.record.WorkbookSubscriptionStatus
import com.few.api.repo.dao.subscription.record.CountAllSubscriptionStatusRecord
import com.few.api.repo.dao.subscription.record.MemberWorkbookSubscriptionStatusRecord
import com.few.api.repo.dao.subscription.record.SubscriptionSendStatusRecord
import com.few.api.repo.dao.subscription.record.*
import jooq.jooq_dsl.Tables.MAPPING_WORKBOOK_ARTICLE
import jooq.jooq_dsl.Tables.SUBSCRIPTION
import jooq.jooq_dsl.tables.MappingWorkbookArticle
import jooq.jooq_dsl.tables.Subscription
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.stereotype.Repository
Expand Down Expand Up @@ -54,6 +52,7 @@ class SubscriptionDao(
dslContext.update(SUBSCRIPTION)
.set(SUBSCRIPTION.DELETED_AT, null as LocalDateTime?)
.set(SUBSCRIPTION.UNSUBS_OPINION, null as String?)
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId))

Expand All @@ -65,6 +64,7 @@ class SubscriptionDao(
fun updateDeletedAtInWorkbookSubscriptionCommand(command: UpdateDeletedAtInWorkbookSubscriptionCommand) =
dslContext.update(SUBSCRIPTION)
.set(SUBSCRIPTION.DELETED_AT, LocalDateTime.now())
.set(Subscription.SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.set(SUBSCRIPTION.UNSUBS_OPINION, command.opinion)
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId))
Expand Down Expand Up @@ -140,6 +140,7 @@ class SubscriptionDao(
fun updateDeletedAtInAllSubscriptionCommand(command: UpdateDeletedAtInAllSubscriptionCommand) =
dslContext.update(SUBSCRIPTION)
.set(SUBSCRIPTION.DELETED_AT, LocalDateTime.now())
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.set(SUBSCRIPTION.UNSUBS_OPINION, command.opinion) // TODO: opinion row 마다 중복 해결
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))

Expand Down Expand Up @@ -191,6 +192,8 @@ class SubscriptionDao(
command: UpdateArticleProgressCommand,
) = dslContext.update(SUBSCRIPTION)
.set(SUBSCRIPTION.PROGRESS, SUBSCRIPTION.PROGRESS.add(1))
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.set(SUBSCRIPTION.SEND_AT, LocalDateTime.now())
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId))

Expand All @@ -202,6 +205,8 @@ class SubscriptionDao(
fun updateLastArticleProgressCommand(command: UpdateLastArticleProgressCommand) =
dslContext.update(SUBSCRIPTION)
.set(SUBSCRIPTION.DELETED_AT, LocalDateTime.now())
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.set(SUBSCRIPTION.SEND_AT, LocalDateTime.now())
.set(SUBSCRIPTION.UNSUBS_OPINION, command.opinion)
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId))
Expand Down Expand Up @@ -258,4 +263,23 @@ class SubscriptionDao(
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.`in`(command.workbookIds))

fun selectSubscriptionTimeRecord(
query: SelectSubscriptionQuery,
): SubscriptionTimeRecord? {
return selectSubscriptionTimeRecordQuery(query)
.fetchOneInto(SubscriptionTimeRecord::class.java)
}

fun selectSubscriptionTimeRecordQuery(query: SelectSubscriptionQuery) =
dslContext.select(
SUBSCRIPTION.MEMBER_ID.`as`(SubscriptionTimeRecord::memberId.name),
SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionTimeRecord::workbookId.name),
SUBSCRIPTION.CREATED_AT.`as`(SubscriptionTimeRecord::createdAt.name),
SUBSCRIPTION.MODIFIED_AT.`as`(SubscriptionTimeRecord::modifiedAt.name),
SUBSCRIPTION.SEND_AT.`as`(SubscriptionTimeRecord::sendAt.name)
)
.from(SUBSCRIPTION)
.where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(query.workbookId))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.repo.dao.subscription.query

data class SelectSubscriptionQuery(
val memberId: Long,
val workbookId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.few.api.repo.dao.subscription.record

import java.time.LocalDateTime

data class SubscriptionTimeRecord(
val memberId: Long,
val workbookId: Long,
val createdAt: LocalDateTime,
val modifiedAt: LocalDateTime,
val sendAt: LocalDateTime?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,18 @@ class SubscriptionDaoExplainGenerateTest : JooqTestSpec() {

ResultGenerator.execute(query, explain, "selectAllSubscriptionSendStatusQueryExplain")
}

@Test
fun selectSubscriptionTimeRecordQueryExplain() {
val query = subscriptionDao.selectSubscriptionTimeRecordQuery(
SelectSubscriptionQuery(
memberId = 1L,
workbookId = 1L
)
)

val explain = ExplainGenerator.execute(dslContext, query)

ResultGenerator.execute(query, explain, "selectSubscriptionTimeRecordQueryExplain")
}
}
49 changes: 38 additions & 11 deletions api/src/main/kotlin/com/few/api/domain/common/lock/LockAspect.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,33 @@ class LockAspect(
getLockFor(joinPoint).run {
when (this.identifier) {
LockIdentifier.SUBSCRIPTION_MEMBER_ID_WORKBOOK_ID -> {
val useCaseIn = joinPoint.args[0] as SubscribeWorkbookUseCaseIn
getSubscriptionMemberIdAndWorkBookIdLock(useCaseIn)
getSubscriptionMemberIdAndWorkBookIdLockCase(joinPoint)
}
}
}
}

private fun getSubscriptionMemberIdAndWorkBookIdLockCase(joinPoint: JoinPoint) {
if (joinPoint.args[0] is SubscribeWorkbookUseCaseIn) {
val useCaseIn = joinPoint.args[0] as SubscribeWorkbookUseCaseIn
getSubscriptionMemberIdAndWorkBookIdLock(useCaseIn)
} else {
val memberId = joinPoint.args[0] as Long
val workbookId = joinPoint.args[1] as Long
getSubscriptionMemberIdAndWorkBookIdLock(memberId, workbookId)
}
}

private fun getSubscriptionMemberIdAndWorkBookIdLock(useCaseIn: SubscribeWorkbookUseCaseIn) {
subscriptionDao.getLock(useCaseIn.memberId, useCaseIn.workbookId).run {
getSubscriptionMemberIdAndWorkBookIdLock(useCaseIn.memberId, useCaseIn.workbookId)
}

private fun getSubscriptionMemberIdAndWorkBookIdLock(memberId: Long, workbookId: Long) {
subscriptionDao.getLock(memberId, workbookId).run {
if (!this) {
throw IllegalStateException("Already in progress for ${useCaseIn.memberId}'s subscription to ${useCaseIn.workbookId}")
throw IllegalStateException("Already in progress for $memberId's subscription to $workbookId")
}
log.debug { "Lock acquired for ${useCaseIn.memberId}'s subscription to ${useCaseIn.workbookId}" }
log.debug { "Lock acquired for $memberId's subscription to $workbookId" }
}
}

Expand All @@ -48,8 +62,7 @@ class LockAspect(
getLockFor(joinPoint).run {
when (this.identifier) {
LockIdentifier.SUBSCRIPTION_MEMBER_ID_WORKBOOK_ID -> {
val useCaseIn = joinPoint.args[0] as SubscribeWorkbookUseCaseIn
releaseSubscriptionMemberIdAndWorkBookIdLock(useCaseIn)
releaseSubscriptionMemberIdAndWorkBookIdLockCase(joinPoint)
}
}
}
Expand All @@ -60,8 +73,7 @@ class LockAspect(
getLockFor(joinPoint).run {
when (this.identifier) {
LockIdentifier.SUBSCRIPTION_MEMBER_ID_WORKBOOK_ID -> {
val useCaseIn = joinPoint.args[0] as SubscribeWorkbookUseCaseIn
releaseSubscriptionMemberIdAndWorkBookIdLock(useCaseIn)
releaseSubscriptionMemberIdAndWorkBookIdLockCase(joinPoint)
}
}
}
Expand All @@ -70,8 +82,23 @@ class LockAspect(
private fun getLockFor(joinPoint: JoinPoint) =
(joinPoint.signature as MethodSignature).method.getAnnotation(LockFor::class.java)

private fun releaseSubscriptionMemberIdAndWorkBookIdLockCase(joinPoint: JoinPoint) {
if (joinPoint.args[0] is SubscribeWorkbookUseCaseIn) {
val useCaseIn = joinPoint.args[0] as SubscribeWorkbookUseCaseIn
releaseSubscriptionMemberIdAndWorkBookIdLock(useCaseIn)
} else {
val memberId = joinPoint.args[0] as Long
val workbookId = joinPoint.args[1] as Long
releaseSubscriptionMemberIdAndWorkBookIdLock(memberId, workbookId)
}
}

private fun releaseSubscriptionMemberIdAndWorkBookIdLock(useCaseIn: SubscribeWorkbookUseCaseIn) {
subscriptionDao.releaseLock(useCaseIn.memberId, useCaseIn.workbookId)
log.debug { "Lock released for ${useCaseIn.memberId}'s subscription to ${useCaseIn.workbookId}" }
releaseSubscriptionMemberIdAndWorkBookIdLock(useCaseIn.memberId, useCaseIn.workbookId)
}

private fun releaseSubscriptionMemberIdAndWorkBookIdLock(memberId: Long, workbookId: Long) {
subscriptionDao.releaseLock(memberId, workbookId)
log.debug { "Lock released for $memberId's subscription to $workbookId" }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package com.few.api.domain.subscription.event
import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent
import com.few.api.domain.subscription.handler.SendWorkbookArticleAsyncHandler
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.event.TransactionPhase
import org.springframework.transaction.event.TransactionalEventListener

Expand All @@ -14,7 +12,6 @@ class WorkbookSubscriptionAfterCompletionEventListener(
) {

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
@Transactional(propagation = Propagation.REQUIRES_NEW)
fun handleEvent(event: WorkbookSubscriptionEvent) {
sendWorkbookArticleAsyncHandler.sendWorkbookArticle(
event.memberId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.few.api.domain.subscription.handler

import com.few.api.config.DatabaseAccessThreadPoolConfig.Companion.DATABASE_ACCESS_POOL
import com.few.api.domain.common.lock.LockFor
import com.few.api.domain.common.lock.LockIdentifier
import com.few.api.domain.subscription.service.SubscriptionArticleService
import com.few.api.domain.subscription.service.SubscriptionMemberService
import com.few.api.domain.subscription.service.SubscriptionEmailService
Expand All @@ -10,11 +12,14 @@ import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.subscription.SubscriptionDao
import com.few.api.repo.dao.subscription.command.UpdateArticleProgressCommand
import com.few.api.repo.dao.subscription.command.UpdateLastArticleProgressCommand
import com.few.api.repo.dao.subscription.query.SelectSubscriptionQuery
import com.few.data.common.code.CategoryType
import com.few.email.service.article.dto.Content
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate

@Component
Expand All @@ -28,8 +33,22 @@ class SendWorkbookArticleAsyncHandler(
private val log = KotlinLogging.logger {}

@Async(value = DATABASE_ACCESS_POOL)
@LockFor(identifier = LockIdentifier.SUBSCRIPTION_MEMBER_ID_WORKBOOK_ID)
@Transactional(propagation = Propagation.REQUIRES_NEW)
fun sendWorkbookArticle(memberId: Long, workbookId: Long, articleDayCol: Byte) {
val date = LocalDate.now()

subscriptionDao.selectSubscriptionTimeRecord(
SelectSubscriptionQuery(
memberId = memberId,
workbookId = workbookId
)
)?.let {
if (it.sendAt?.isAfter(date.atStartOfDay()) == true) {
return
}
}

val memberEmail = memberService.readMemberEmail(ReadMemberEmailInDto(memberId))?.email
?: throw NotFoundException("member.notfound.id")
val article = articleService.readArticleIdByWorkbookIdAndDay(
Expand Down Expand Up @@ -70,18 +89,18 @@ class SendWorkbookArticleAsyncHandler(
)
)?.lastArticleId ?: throw NotFoundException("workbook.notfound.id")

if (article.id == lastDayArticleId) {
if (article.id != lastDayArticleId) {
subscriptionDao.updateArticleProgress(
UpdateArticleProgressCommand(
workbookId,
memberId
memberId,
workbookId
)
)
} else {
subscriptionDao.updateLastArticleProgress(
UpdateLastArticleProgressCommand(
workbookId,
memberId
memberId,
workbookId
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ class WorkBookSubscriberWriter(
updateQueries.add(
dslContext.update(Subscription.SUBSCRIPTION)
.set(Subscription.SUBSCRIPTION.PROGRESS, updateTargetMemberRecord.updatedProgress)
.set(Subscription.SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.set(Subscription.SUBSCRIPTION.SEND_AT, LocalDateTime.now())
.where(Subscription.SUBSCRIPTION.MEMBER_ID.eq(updateTargetMemberRecord.memberId))
.and(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(updateTargetMemberRecord.targetWorkBookId))
)
Expand All @@ -101,6 +103,8 @@ class WorkBookSubscriberWriter(
receiveLastDayQueries.add(
dslContext.update(Subscription.SUBSCRIPTION)
.set(Subscription.SUBSCRIPTION.DELETED_AT, LocalDateTime.now())
.set(Subscription.SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.set(Subscription.SUBSCRIPTION.SEND_AT, LocalDateTime.now())
.set(Subscription.SUBSCRIPTION.UNSUBS_OPINION, "receive.all")
.where(Subscription.SUBSCRIPTION.MEMBER_ID.eq(receiveLastDayMember.memberId))
.and(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(receiveLastDayMember.targetWorkBookId))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- 구독 발송 시간 컬럼 추가
ALTER TABLE SUBSCRIPTION ADD COLUMN send_at TIMESTAMP;

0 comments on commit 22d9abb

Please sign in to comment.