-
Notifications
You must be signed in to change notification settings - Fork 0
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/#34. 유저 활동 데이터 누적 AOP로 처리 #38
Merged
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
1ccbc45
🔥 의미없는 로그 제거
RokwonK c83cbe2
🎨 Question관련 Entity 내 non-nullable 필드들 타입 수정
RokwonK 66aedb2
✨ Question Open 검색 쿼리 추가
RokwonK 472591c
✨ Question Get 및 발급 시 발생가능한 예외 추가
RokwonK 49cc146
✨ Question Get & 발급에 필요한 쿼리추가
RokwonK b783a6c
✨ Question Domain 내 응답 Dto 추가
RokwonK d41137c
✨ Question 발급 도메인 로직 구현
RokwonK 058d64a
✨ Question 가져오기 도메인 로직 구현
RokwonK a794d36
✨ Question Application Service 로직 구현
RokwonK aa47f73
✨ Question GET API 구현
RokwonK 673aac5
✨ aop 의존성 추가
RokwonK 3334df3
✨ Domain 내 Lock 관련 예외 추가
RokwonK 5f7657e
✨ Lock에 사용될 Key interface 정의
RokwonK 895dca1
✨ Lock을 사용할 메서드에 적용될 어노테이션 추가
RokwonK e47162d
✨ Lock 저장소 인터페이스 및 SameOrigin 구현체 추가
RokwonK 9ca37fd
✨ 분산락 AOP 구현
RokwonK 2fd1e19
✨ 오늘 Question 검색&발급 메서드에 분산락 적용
RokwonK 84a5cde
✨ DistributedLockKey -> LockKey로 이름 변경
RokwonK 85006e5
🎨 코드의 구조 / 형태 개선
RokwonK 4fd4145
🎨 update submodule
RokwonK 870b801
🎨 DistributedLockRepository 메서드 추가
RokwonK 6d42c0f
✨ 새로운 Error 추가
RokwonK 06f73a8
🔥 TODO 삭제
RokwonK b786013
✨ LockDataSource 어노테이션 추가
RokwonK 7c667fe
✨ Lock 용 데이터소스 나누기
RokwonK 548b529
✨ DistributedLockAdvisor 구현
RokwonK 7056c8e
✨ SameOriginLockRepository 구현 완료
RokwonK 5b53fb5
✨ 질문 발급 시 user의 question Cnt 올리기
RokwonK 44773c0
✨ Domain 모듈 Test Fixture 추가(Member, Question)
RokwonK e332c7b
✅ issueQuestion 테스트 코드 추가
RokwonK 8a2be3c
✅ 오늘의 문제, 문제 조회 테스트 코드 추가
RokwonK fe161e0
✅ 오늘의 문제 발급&조회 동시성 테스트 추가
RokwonK e12b735
Merge branch 'develop' of https://github.com/kids-ground/adevspoon-ba…
RokwonK 0c68769
✨ AOP 우선순위 조절을 위한 트랜잭션 AOP 우선순위 조절
RokwonK 3384684
✨ Activity 이벤트 정의
RokwonK dac7cce
✨ Activity Event Handler 추가
RokwonK 570fc4a
✨ Activity Repository 내 쿼리 추가
RokwonK e5f5004
✨ Activity Event AOP 추가
RokwonK 79b9f82
✨ BoardPost 생성 시 Activity Event 추가
RokwonK File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
adevspoon-domain/src/main/kotlin/com/adevspoon/domain/common/annotation/ActivityEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.adevspoon.domain.common.annotation | ||
|
||
@Target(AnnotationTarget.FUNCTION) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
annotation class ActivityEvent( | ||
val type: ActivityEventType | ||
) |
7 changes: 7 additions & 0 deletions
7
adevspoon-domain/src/main/kotlin/com/adevspoon/domain/common/annotation/ActivityEventType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.adevspoon.domain.common.annotation | ||
|
||
enum class ActivityEventType { | ||
ATTENDANCE, | ||
ANSWER, | ||
BOARD_POST, | ||
} |
55 changes: 55 additions & 0 deletions
55
adevspoon-domain/src/main/kotlin/com/adevspoon/domain/common/aop/ActivityEventAdvisor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.adevspoon.domain.common.aop | ||
|
||
import com.adevspoon.domain.board.dto.response.BoardPost | ||
import com.adevspoon.domain.common.annotation.ActivityEvent | ||
import com.adevspoon.domain.common.annotation.ActivityEventType | ||
import com.adevspoon.domain.common.event.BoardPostActivityEvent | ||
import org.aspectj.lang.JoinPoint | ||
import org.aspectj.lang.annotation.AfterReturning | ||
import org.aspectj.lang.annotation.Aspect | ||
import org.aspectj.lang.reflect.MethodSignature | ||
import org.springframework.context.ApplicationEventPublisher | ||
import org.springframework.core.Ordered | ||
import org.springframework.core.annotation.Order | ||
import org.springframework.stereotype.Component | ||
|
||
|
||
@Aspect | ||
@Component | ||
@Order(Ordered.LOWEST_PRECEDENCE) | ||
class ActivityEventAdvisor( | ||
private val eventPublisher: ApplicationEventPublisher | ||
) { | ||
@AfterReturning(pointcut = "@annotation(com.adevspoon.domain.common.annotation.ActivityEvent)", returning = "result") | ||
fun afterReturningAdvice(joinPoint: JoinPoint, result: Any?) { | ||
val activityAnnotation = getAnnotation(joinPoint) | ||
|
||
when(activityAnnotation.type) { | ||
ActivityEventType.ATTENDANCE -> attendanceEventPublish(result) | ||
ActivityEventType.ANSWER -> answerEventPublish(result) | ||
ActivityEventType.BOARD_POST -> boardPostEventPublish(result) | ||
} | ||
} | ||
|
||
private fun getAnnotation(joinPoint: JoinPoint) = | ||
(joinPoint.signature as MethodSignature).method | ||
.getAnnotation(ActivityEvent::class.java) | ||
|
||
private fun boardPostEventPublish(result: Any?) { | ||
(result as? BoardPost)?.let { | ||
eventPublisher.publishEvent(BoardPostActivityEvent(it.user.memberId)) | ||
} | ||
} | ||
|
||
private fun attendanceEventPublish(result: Any?) { | ||
TODO(""" | ||
Implement the logic to publish the ATTENDANCE event | ||
""".trimIndent()) | ||
} | ||
|
||
private fun answerEventPublish(result: Any?) { | ||
TODO(""" | ||
Implement the logic to publish the ANSWER event | ||
""".trimIndent()) | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
adevspoon-domain/src/main/kotlin/com/adevspoon/domain/common/event/AnswerActivityEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.adevspoon.domain.common.event | ||
|
||
data class AnswerActivityEvent( | ||
val memberId: Long, | ||
val answerId: Long, | ||
) |
5 changes: 5 additions & 0 deletions
5
...spoon-domain/src/main/kotlin/com/adevspoon/domain/common/event/AttendanceActivityEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.adevspoon.domain.common.event | ||
|
||
data class AttendanceActivityEvent( | ||
val memberId: Long | ||
) |
5 changes: 5 additions & 0 deletions
5
adevspoon-domain/src/main/kotlin/com/adevspoon/domain/common/event/BoardPostActivityEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.adevspoon.domain.common.event | ||
|
||
data class BoardPostActivityEvent( | ||
val memberId: Long, | ||
) |
8 changes: 8 additions & 0 deletions
8
adevspoon-domain/src/main/kotlin/com/adevspoon/domain/config/AsyncConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.adevspoon.domain.config | ||
|
||
import org.springframework.context.annotation.Configuration | ||
import org.springframework.scheduling.annotation.EnableAsync | ||
|
||
@Configuration | ||
@EnableAsync | ||
class AsyncConfig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
...n-domain/src/main/kotlin/com/adevspoon/domain/member/repository/UserActivityRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,18 @@ | ||
package com.adevspoon.domain.member.repository | ||
|
||
import com.adevspoon.domain.member.domain.UserActivityEntity | ||
import jakarta.persistence.LockModeType | ||
import org.springframework.data.jpa.repository.JpaRepository | ||
import org.springframework.data.jpa.repository.Lock | ||
import org.springframework.data.jpa.repository.Modifying | ||
import org.springframework.data.jpa.repository.Query | ||
|
||
interface UserActivityRepository : JpaRepository<UserActivityEntity, Long> { | ||
@Modifying(clearAutomatically = true) | ||
@Query("UPDATE UserActivityEntity u SET u.boardPostCount = u.boardPostCount + 1 WHERE u.id = :userId") | ||
fun increaseBoardPostCount(userId: Long): Int | ||
|
||
@Lock(LockModeType.PESSIMISTIC_WRITE) | ||
@Query("SELECT u FROM UserActivityEntity u WHERE u.id = :userid") | ||
fun findByIdWithLock(userid: Long): UserActivityEntity? | ||
} |
42 changes: 42 additions & 0 deletions
42
...-domain/src/main/kotlin/com/adevspoon/domain/member/service/MemberActivityEventHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.adevspoon.domain.member.service | ||
|
||
import com.adevspoon.domain.common.annotation.DomainService | ||
import com.adevspoon.domain.common.event.AnswerActivityEvent | ||
import com.adevspoon.domain.common.event.AttendanceActivityEvent | ||
import com.adevspoon.domain.common.event.BoardPostActivityEvent | ||
import com.adevspoon.domain.member.repository.UserActivityRepository | ||
import jakarta.transaction.Transactional | ||
import org.springframework.scheduling.annotation.Async | ||
import org.springframework.transaction.event.TransactionPhase | ||
import org.springframework.transaction.event.TransactionalEventListener | ||
|
||
@DomainService | ||
class MemberActivityEventHandler( | ||
private val userActivityRepository: UserActivityRepository, | ||
) { | ||
|
||
@Async | ||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
@Transactional | ||
fun handleAttendanceEvent(event: AttendanceActivityEvent) { | ||
TODO(""" | ||
Implement the logic to handle the ATTENDANCE event | ||
""".trimIndent()) | ||
} | ||
|
||
@Async | ||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
@Transactional | ||
fun handleAnswerEvent(event: AnswerActivityEvent) { | ||
TODO(""" | ||
Implement the logic to handle the ANSWER event | ||
""".trimIndent()) | ||
} | ||
|
||
@Async | ||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
@Transactional | ||
fun handleBoardPostEvent(event: BoardPostActivityEvent) { | ||
userActivityRepository.increaseBoardPostCount(event.memberId) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UserActivityEntity는 유저가 어떤 활동을 했을 때에만 데이터가 변경되는 것 같아요. 그렇다면 충돌 가능성이 매우 낮을 거라고 생각되고, 이럴 때는 낙관적 락이 적절하다고 생각해요. 비관적 락을 고민한 이유도 같이 적어주시면 이해해 도움이 될 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
낙관적 락을 사용하면 버전을 명시하는 필드가 필요하다는 점, 무엇보다도 재시도 처리 로직도 들어가야한다는 점이 걸렸습니다.
또한 요청이 많지 않을 것이기 때문에 락으로 인한 성능 이슈가 크지는 않을 듯 했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요청의 빈도가 낮으면 비관적 락을 사용해도 성능에 미치는 영향이 미미하니 그대로 사용해도 좋을 것 같아요 :)