-
Notifications
You must be signed in to change notification settings - Fork 1
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
[BSVR-217] 리뷰 공감에 분산락 추가 #166
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
0d82f11
build: embedded redis dependency 추가
EunjiShin 7ecb17d
feat: embedded redis 설정 파일 작성
EunjiShin 9f94e75
refactor: OS 종속적이지 않게 수정
EunjiShin 835679e
feat: 분산락 어노테이션 추가
EunjiShin b016ff1
build: common 모듈에 aop dependency 추가
EunjiShin 7b993bb
feat: 분산락 AOP 구현
EunjiShin 69900fc
feat: 리뷰 공감 메서드에 분산락 적용
EunjiShin 0f6170a
test: FakeReviewLikeRepository 생성
EunjiShin e855e3f
test: reviewLikeService 생성
EunjiShin 5150605
test: 동시성 테스트 유틸 추가
EunjiShin 7cb393a
refactor: sout -> log로 변경
EunjiShin 16b1e46
test: 리뷰 공감 테스트코드 작성
EunjiShin 5b2d6f6
test: 리뷰 공감에 필요한 repository 구현
EunjiShin 65ec2ab
build: testContainer 의존성 설정
EunjiShin dc07697
refactor: @Value를 Properties로 대체
EunjiShin ae2808e
feat: test용 프로필 생성
EunjiShin 1c64294
test: 리뷰 공감 테스트에 필요한 데이터
EunjiShin 76f89f4
fix: ColumnDefault 수정
EunjiShin d6e9a13
test: 리뷰 공감 테스트 추가
EunjiShin 82b7b06
fix: resolve merge conflict
EunjiShin db92ca9
feat: 불필요한 코드 삭제
EunjiShin f73237b
feat: 불필요한 코드 삭제
EunjiShin 2efb503
test: 통합 테스트로 바꾸면서 불필요해진 fake 삭제
EunjiShin 179c6eb
test: reviewLikeRepository fake 보완
EunjiShin e3f5039
fix: resolve merge conflict
EunjiShin 0eb7ef5
feat: oauth property 추가
EunjiShin 0683484
refactor: 불필요한 fake 삭제
EunjiShin 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
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
6 changes: 6 additions & 0 deletions
6
application/src/main/java/org/depromeet/spot/application/common/jwt/JwtProperties.java
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 org.depromeet.spot.application.common.jwt; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
@ConfigurationProperties(prefix = "spring.jwt") | ||
public record JwtProperties(String secret) {} |
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
119 changes: 119 additions & 0 deletions
119
application/src/test/java/org/depromeet/spot/application/ReviewLikeServiceTest.java
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,119 @@ | ||
package org.depromeet.spot.application; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
import org.depromeet.spot.domain.member.Level; | ||
import org.depromeet.spot.domain.member.Member; | ||
import org.depromeet.spot.domain.member.enums.MemberRole; | ||
import org.depromeet.spot.domain.member.enums.SnsProvider; | ||
import org.depromeet.spot.domain.review.Review; | ||
import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase; | ||
import org.depromeet.spot.usecase.port.out.member.LevelRepository; | ||
import org.depromeet.spot.usecase.port.out.member.MemberRepository; | ||
import org.depromeet.spot.usecase.service.review.like.ReviewLikeService; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.test.context.ActiveProfiles; | ||
import org.springframework.test.context.TestPropertySource; | ||
import org.springframework.test.context.jdbc.Sql; | ||
import org.springframework.test.context.jdbc.Sql.ExecutionPhase; | ||
import org.springframework.test.context.jdbc.SqlGroup; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import org.testcontainers.junit.jupiter.Testcontainers; | ||
|
||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
@SpringBootTest | ||
@Testcontainers | ||
@ActiveProfiles("test") | ||
@TestPropertySource("classpath:application-test.yml") | ||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) | ||
@SqlGroup({ | ||
@Sql( | ||
value = "/sql/delete-data-after-review-like.sql", | ||
executionPhase = ExecutionPhase.AFTER_TEST_METHOD), | ||
@Sql( | ||
value = "/sql/review-like-service-data.sql", | ||
executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), | ||
}) | ||
class ReviewLikeServiceTest { | ||
|
||
@Autowired private ReviewLikeService reviewLikeService; | ||
|
||
@Autowired private ReadReviewUsecase readReviewUsecase; | ||
|
||
@Autowired private MemberRepository memberRepository; | ||
|
||
@Autowired private LevelRepository levelRepository; | ||
|
||
private static final int NUMBER_OF_THREAD = 100; | ||
|
||
@BeforeEach | ||
@Transactional | ||
void init() { | ||
Level level = levelRepository.findByValue(0); | ||
AtomicLong memberIdGenerator = new AtomicLong(1); | ||
|
||
for (int i = 0; i < NUMBER_OF_THREAD; i++) { | ||
long memberId = memberIdGenerator.getAndIncrement(); | ||
memberRepository.save( | ||
Member.builder() | ||
.id(memberId) | ||
.snsProvider(SnsProvider.KAKAO) | ||
.teamId(1L) | ||
.role(MemberRole.ROLE_ADMIN) | ||
.idToken("idToken" + memberId) | ||
.nickname(String.valueOf(memberId)) | ||
.phoneNumber(String.valueOf(memberId)) | ||
.email("email" + memberId) | ||
.build(), | ||
level); | ||
} | ||
} | ||
|
||
@Test | ||
void 멀티_스레드_환경에서_리뷰_공감_수를_정상적으로_증가시킬_수_있다() throws InterruptedException { | ||
// given | ||
final long reviewId = 1L; | ||
AtomicLong memberIdGenerator = new AtomicLong(1); | ||
final ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREAD); | ||
final CountDownLatch latch = new CountDownLatch(NUMBER_OF_THREAD); | ||
|
||
// when | ||
for (int i = 0; i < NUMBER_OF_THREAD; i++) { | ||
long memberId = memberIdGenerator.getAndIncrement(); | ||
executorService.execute( | ||
() -> { | ||
try { | ||
reviewLikeService.toggleLike(memberId, reviewId); | ||
System.out.println( | ||
"Thread " + Thread.currentThread().getId() + " - 성공"); | ||
} catch (Throwable e) { | ||
System.out.println( | ||
"Thread " | ||
+ Thread.currentThread().getId() | ||
+ " - 실패" | ||
+ e.getClass().getName()); | ||
e.printStackTrace(); | ||
} finally { | ||
latch.countDown(); | ||
} | ||
}); | ||
} | ||
latch.await(); | ||
executorService.shutdown(); | ||
|
||
// then | ||
Review review = readReviewUsecase.findById(reviewId); | ||
assertEquals(100, review.getLikesCount()); | ||
} | ||
} |
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,45 @@ | ||
loki: | ||
url: ${LOKI_URL} | ||
|
||
aws: | ||
s3: | ||
accessKey: ${AWS_S3_ACCESS_KEY} | ||
secretKey: ${AWS_S3_SECRET_KEY} | ||
bucketName: ${AWS_S3_BUCKET_NAME} | ||
redis: | ||
host: localhost | ||
port: 6379 | ||
|
||
oauth: | ||
kakaoClientId: ${KAKAO_CLIENT_ID} | ||
kakaoAuthTokenUrlHost: ${KAKAO_AUTH_TOKEN_URL_HOST} | ||
kakaoAuthUserUrlHost: ${KAKAO_AUTH_USER_URL_HOST} | ||
kakaoRedirectUrl: ${KAKAO_REDIRECT_URL} | ||
googleClientId: ${GOOGLE_CLIENT_ID} | ||
googleClientSecret: ${GOOGLE_CLIENT_SECRET} | ||
googleRedirectUrl: ${GOOGLE_REDIRECT_URL} | ||
googleAuthTokenUrlHost: ${GOOGLE_AUTH_TOKEN_URL_HOST} | ||
googleUserUrlHost: ${GOOGLE_USER_URL_HOST} | ||
|
||
spring: | ||
datasource: | ||
url: jdbc:tc:mysql:8.0.36:///testdb | ||
username: testuser | ||
password: testpassword | ||
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver | ||
hikari: | ||
connection-timeout: 100000 | ||
maximum-pool-size: 300 | ||
max-lifetime: 100000 | ||
jpa: | ||
database: mysql | ||
hibernate: | ||
ddl-auto: create | ||
database-platform: org.hibernate.dialect.MySQL8Dialect | ||
defer-datasource-initialization: true | ||
jwt: | ||
secret: ${JWT_SECRETKEY} | ||
|
||
|
||
server: | ||
port: 8080 |
10 changes: 10 additions & 0 deletions
10
application/src/test/resources/sql/delete-data-after-review-like.sql
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,10 @@ | ||
delete from members where id > 0; | ||
delete from reviews where id > 0; | ||
delete from review_likes where id > 0; | ||
delete from levels where id > 0; | ||
delete from stadiums where id > 0; | ||
delete from baseball_teams where id > 0; | ||
delete from sections where id > 0; | ||
delete from blocks where id > 0; | ||
delete from block_rows where id > 0; | ||
delete from seats where id > 0; |
41 changes: 41 additions & 0 deletions
41
application/src/test/resources/sql/review-like-service-data.sql
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,41 @@ | ||
-- levels | ||
INSERT INTO levels (id, value, title, mascot_image_url, created_at, updated_at, deleted_at) | ||
VALUES (1, 0, '직관 꿈나무', null, null, null, null), | ||
(2, 1, '직관 첫 걸음', null, null, null, null), | ||
(3, 2, '경기장 탐험가', null, null, null, null), | ||
(4, 3, '직관의 여유', null, null, null, null), | ||
(5, 4, '응원 단장', null, null, null, null), | ||
(6, 5, '야구장 VIP', null, null, null, null), | ||
(7, 6, '전설의 직관러', null, null, null, null); | ||
|
||
-- Stadiums | ||
INSERT INTO stadiums (id, name, main_image, seating_chart_image, labeled_seating_chart_image, | ||
is_active) | ||
VALUES (1, '잠실 야구 경기장', 'main_image_a.jpg', 'seating_chart_a.jpg', 'labeled_seating_chart_a.jpg', | ||
1); | ||
|
||
-- Baseball Teams | ||
INSERT INTO baseball_teams (id, name, alias, logo, label_font_color) | ||
VALUES (1, 'Team A', 'A', 'logo_a.png', '#FFFFFF'); | ||
|
||
-- Stadium Sections | ||
INSERT INTO sections (id, stadium_id, name, alias) | ||
VALUES (1, 1, '오렌지석', '응원석'); | ||
|
||
-- Block | ||
INSERT INTO blocks (id, stadium_id, section_id, code, max_rows) | ||
VALUES (1, 1, 1, "207", 3); | ||
|
||
-- Row | ||
INSERT INTO block_rows (id, block_id, number, max_seats) | ||
VALUES (1, 1, 1, 3); | ||
|
||
-- Seats | ||
INSERT INTO seats (id, stadium_id, section_id, block_id, row_id, seat_number) | ||
VALUES (1, 1, 1, 1, 1, 1); | ||
|
||
-- reviews | ||
INSERT INTO reviews (id, member_id, stadium_id, section_id, block_id, row_id, seat_id, date_time, content, likes_count, scraps_count, review_type) | ||
VALUES | ||
(1, 1, 1, 1, 1, 1, 1, '2023-06-01 19:00:00', '좋은 경기였습니다!', 0, 0, 'VIEW'), | ||
(2, 1, 1, 1, 1, 1, 1, '2023-06-01 19:00:00', '좋은 경기였습니다!', 0, 0, 'VIEW'); |
20 changes: 20 additions & 0 deletions
20
common/src/main/java/org/depromeet/spot/common/annotation/DistributedLock.java
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,20 @@ | ||
package org.depromeet.spot.common.annotation; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
@Target(ElementType.METHOD) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface DistributedLock { | ||
|
||
String key(); | ||
|
||
TimeUnit timeUnit() default TimeUnit.SECONDS; | ||
|
||
long leaseTime() default 5L; | ||
|
||
long waitTime() default 5L; | ||
} |
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Properties 변환 굿이에용