From 59293b7a3b8e84388a798405392e00c752ecc3af Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 15:40:36 +0900 Subject: [PATCH 01/18] =?UTF-8?q?chore:=20build.gradle=EC=97=90=20Redis=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/build.gradle b/backend/build.gradle index 174f27360..1ce60163f 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -48,6 +48,10 @@ dependencies { implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'it.ozimov.embedded-redis:0.7.2' + asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.boot:spring-boot-starter-test' From 627479cc3e6ad1a68c73447dbd2020e52a6a18fc Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 18:28:34 +0900 Subject: [PATCH 02/18] =?UTF-8?q?test:=20CouponEvent=EC=99=80=20CouponHist?= =?UTF-8?q?ory=EC=97=90=20=EB=8C=80=ED=95=9C=20Fixture=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/domain/CouponEventFixture.java | 27 +++++++++++++++++++ .../fixture/domain/CouponHistoryFixture.java | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponEventFixture.java create mode 100644 backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponHistoryFixture.java diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponEventFixture.java b/backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponEventFixture.java new file mode 100644 index 000000000..df12eca62 --- /dev/null +++ b/backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponEventFixture.java @@ -0,0 +1,27 @@ +package com.woowacourse.kkogkkog.support.fixture.domain; + +import com.woowacourse.kkogkkog.coupon.domain.CouponEvent; +import com.woowacourse.kkogkkog.coupon.domain.CouponEventType; +import java.time.LocalDateTime; + +public enum CouponEventFixture { + + INIT(CouponEventType.INIT, null), + REQUEST(CouponEventType.REQUEST, LocalDateTime.of(2023, 9, 7, 0, 0, 0)), + CANCEL(CouponEventType.CANCEL, null), + DECLINE(CouponEventType.DECLINE, null), + ACCEPT(CouponEventType.ACCEPT, null), + FINISH(CouponEventType.FINISH, null); + + private final CouponEventType eventType; + private final LocalDateTime meetingDate; + + CouponEventFixture(final CouponEventType eventType, final LocalDateTime meetingDate) { + this.eventType = eventType; + this.meetingDate = meetingDate; + } + + public CouponEvent getCouponEvent() { + return new CouponEvent(eventType, meetingDate); + } +} diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponHistoryFixture.java b/backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponHistoryFixture.java new file mode 100644 index 000000000..7ac6cfd2e --- /dev/null +++ b/backend/src/test/java/com/woowacourse/kkogkkog/support/fixture/domain/CouponHistoryFixture.java @@ -0,0 +1,27 @@ +package com.woowacourse.kkogkkog.support.fixture.domain; + +import com.woowacourse.kkogkkog.coupon.domain.Coupon; +import com.woowacourse.kkogkkog.coupon.domain.CouponHistory; +import com.woowacourse.kkogkkog.member.domain.Member; + +public enum CouponHistoryFixture { + + INIT(CouponEventFixture.INIT, "생성 메시지"), + REQUEST(CouponEventFixture.REQUEST, "요청 메시지"), + CANCEL(CouponEventFixture.CANCEL, "취소 메시지"), + DECLINE(CouponEventFixture.DECLINE, "거절 메시지"), + ACCEPT(CouponEventFixture.ACCEPT, "허락 메시지"), + FINISH(CouponEventFixture.FINISH, "완료 메시지"); + + private final CouponEventFixture couponEvent; + private final String message; + + CouponHistoryFixture(final CouponEventFixture couponEvent, final String message) { + this.couponEvent = couponEvent; + this.message = message; + } + + public CouponHistory getCouponHistory(Member member, Coupon coupon) { + return CouponHistory.of(member, coupon, couponEvent.getCouponEvent(), message); + } +} From 3b11ce0e4905ac8e0300aba868af85c01452cc4b Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 18:30:54 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20HashMap=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=EC=9D=98=20=EC=9D=BD=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B0=9C=EC=88=98=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=BA=90=EC=89=AC=20=EC=A0=80=EC=9E=A5=EC=86=8C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnreadNoticeCountCacheRepository.java | 29 +++++++ .../UnreadNoticeCountRepositoryTest.java | 83 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java create mode 100644 backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java new file mode 100644 index 000000000..c294f30f6 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java @@ -0,0 +1,29 @@ +package com.woowacourse.kkogkkog.coupon.domain.repository; + +import com.woowacourse.kkogkkog.member.domain.Member; +import java.util.HashMap; +import org.springframework.stereotype.Repository; + +@Repository +public class UnreadNoticeCountCacheRepository { + + private final HashMap unreadCountCache = new HashMap<>(); + private final CouponHistoryRepository couponHistoryRepository; + + public UnreadNoticeCountCacheRepository(CouponHistoryRepository couponHistoryRepository) { + this.couponHistoryRepository = couponHistoryRepository; + } + + public Long get(Member member) { + Long memberId = member.getId(); + if (!unreadCountCache.containsKey(memberId)) { + updateCache(member); + } + return unreadCountCache.get(memberId); + } + + private void updateCache(Member member) { + Long validUnreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); + unreadCountCache.put(member.getId(), validUnreadCount); + } +} diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java new file mode 100644 index 000000000..72ec5faac --- /dev/null +++ b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java @@ -0,0 +1,83 @@ +package com.woowacourse.kkogkkog.coupon.domain.repository; + +import static com.woowacourse.kkogkkog.support.fixture.domain.CouponHistoryFixture.DECLINE; +import static com.woowacourse.kkogkkog.support.fixture.domain.CouponHistoryFixture.INIT; +import static com.woowacourse.kkogkkog.support.fixture.domain.CouponHistoryFixture.REQUEST; +import static com.woowacourse.kkogkkog.support.fixture.domain.MemberFixture.RECEIVER; +import static com.woowacourse.kkogkkog.support.fixture.domain.MemberFixture.SENDER; +import static com.woowacourse.kkogkkog.support.fixture.domain.WorkspaceFixture.KKOGKKOG; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.woowacourse.kkogkkog.coupon.domain.Coupon; +import com.woowacourse.kkogkkog.member.domain.Member; +import com.woowacourse.kkogkkog.member.domain.Workspace; +import com.woowacourse.kkogkkog.member.domain.repository.MemberRepository; +import com.woowacourse.kkogkkog.member.domain.repository.WorkspaceRepository; +import com.woowacourse.kkogkkog.support.fixture.domain.CouponFixture; +import com.woowacourse.kkogkkog.support.repository.RepositoryTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@RepositoryTest +class UnreadNoticeCountRepositoryTest { + + @Autowired + private CouponHistoryRepository couponHistoryRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private WorkspaceRepository workspaceRepository; + + @Autowired + private CouponRepository couponRepository; + + private UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository; + + private Member sender; + private Member receiver; + + @BeforeEach + void setUp() { + Workspace workspace = workspaceRepository.save(KKOGKKOG.getWorkspace()); + sender = memberRepository.save(SENDER.getMember(workspace)); + receiver = memberRepository.save(RECEIVER.getMember(workspace)); + unreadNoticeCountCacheRepository = + new UnreadNoticeCountCacheRepository(couponHistoryRepository); + } + + @Nested + @DisplayName("get 메서드는") + class Get { + + @Test + @DisplayName("유효한 캐쉬가 존재하지 않는 경우 최신 데이터를 조회하여 반환한다.") + void updateAndReadCache() { + Coupon coupon = couponRepository.save(CouponFixture.COFFEE.getCoupon(sender, receiver)); + couponHistoryRepository.save(INIT.getCouponHistory(sender, coupon)); + couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); + couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); + + Long unreadCount = unreadNoticeCountCacheRepository.get(receiver); + assertThat(unreadCount).isEqualTo(2); + } + + @Test + @DisplayName("유효한 캐쉬가 이미 존재하는 경우, 해당 데이터를 그대로 반환한다.") + void readExistingCache() { + Coupon coupon = couponRepository.save(CouponFixture.COFFEE.getCoupon(sender, receiver)); + couponHistoryRepository.save(INIT.getCouponHistory(sender, coupon)); + couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); + couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); + + Long actual = unreadNoticeCountCacheRepository.get(receiver); + Long expected = unreadNoticeCountCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(expected); + } + } +} From fba58daed62832719a12b338f9cbeb5afb1e9f3e Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 18:40:19 +0900 Subject: [PATCH 04/18] =?UTF-8?q?feat:=20=EC=9D=BD=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EA=B0=9C=EC=88=98=20=EC=BA=90?= =?UTF-8?q?=EC=89=AC=20=EA=B0=92=EC=9D=84=20=EC=A6=9D=EA=B0=80=EC=8B=9C?= =?UTF-8?q?=ED=82=A4=EA=B3=A0=20=EC=B4=88=EA=B8=B0=ED=99=94=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnreadNoticeCountCacheRepository.java | 8 ++++ .../UnreadNoticeCountRepositoryTest.java | 45 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java index c294f30f6..d32550c13 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java @@ -26,4 +26,12 @@ private void updateCache(Member member) { Long validUnreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); unreadCountCache.put(member.getId(), validUnreadCount); } + + public void increment(Member member) { + unreadCountCache.put(member.getId(), get(member) + 1); + } + + public void reset(Member member) { + unreadCountCache.put(member.getId(), 0L); + } } diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java index 72ec5faac..c9c042aa2 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java @@ -80,4 +80,49 @@ void readExistingCache() { assertThat(actual).isEqualTo(expected); } } + + @Nested + @DisplayName("increment 메서드는") + class Increment { + + @Test + @DisplayName("캐쉬 데이터의 값을 1만큼 증가시킨다.") + void readAndUpdate() { + Long previousCount = unreadNoticeCountCacheRepository.get(receiver); + + unreadNoticeCountCacheRepository.increment(receiver); + Long actual = unreadNoticeCountCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(previousCount + 1); + } + } + + @Nested + @DisplayName("reset 메서드는") + class Reset { + + @Test + @DisplayName("캐쉬 데이터의 값을 0으로 초기화한다.") + void resetToZero() { + Coupon coupon = couponRepository.save(CouponFixture.COFFEE.getCoupon(sender, receiver)); + couponHistoryRepository.save(INIT.getCouponHistory(sender, coupon)); + couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); + couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); + unreadNoticeCountCacheRepository.get(receiver); + + unreadNoticeCountCacheRepository.reset(receiver); + Long actual = unreadNoticeCountCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(0); + } + + @Test + @DisplayName("캐쉬 데이터가 없는 경우 0으로 초기화한다.") + void initWithZero() { + unreadNoticeCountCacheRepository.reset(receiver); + Long actual = unreadNoticeCountCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(0); + } + } } From e105c38333a4bdeee75e8842cb6bf13cdb005d08 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 18:48:32 +0900 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20=EC=9D=BD=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EA=B0=9C=EC=88=98=20=EC=BA=90?= =?UTF-8?q?=EC=89=AC=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnreadNoticeCountCacheRepository.java | 7 +++++ .../UnreadNoticeCountRepositoryTest.java | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java index d32550c13..f88a40261 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java @@ -31,6 +31,13 @@ public void increment(Member member) { unreadCountCache.put(member.getId(), get(member) + 1); } + public void decrement(Member member) { + Long currentUpdateCount = get(member); + if (currentUpdateCount > 0) { + unreadCountCache.put(member.getId(), currentUpdateCount - 1); + } + } + public void reset(Member member) { unreadCountCache.put(member.getId(), 0L); } diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java index c9c042aa2..fc6f57a0d 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java @@ -97,6 +97,37 @@ void readAndUpdate() { } } + @Nested + @DisplayName("decrement 메서드는") + class Decrement { + + @Test + @DisplayName("캐쉬 데이터의 값을 1만큼 감소시킨다.") + void readAndUpdate() { + Coupon coupon = couponRepository.save(CouponFixture.COFFEE.getCoupon(sender, receiver)); + couponHistoryRepository.save(INIT.getCouponHistory(sender, coupon)); + couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); + couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); + Long previousCount = unreadNoticeCountCacheRepository.get(receiver); + + unreadNoticeCountCacheRepository.decrement(receiver); + Long actual = unreadNoticeCountCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(previousCount - 1); + } + + @Test + @DisplayName("캐쉬 데이터의 값이 0인 경우 감소시키지 않는다.") + void minValueZero() { + Long previousCount = unreadNoticeCountCacheRepository.get(receiver); + + unreadNoticeCountCacheRepository.decrement(receiver); + Long actual = unreadNoticeCountCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(previousCount); + } + } + @Nested @DisplayName("reset 메서드는") class Reset { From c2098e351ad6fe5659d1f2b1edcfbef4abc8c81f Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 19:43:53 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20=EC=BA=90=EC=8B=9C=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EC=9D=BD=EC=A7=80=20=EC=95=8A=EC=9D=80=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EA=B0=9C=EC=88=98=20=EA=B3=84=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coupon/application/CouponService.java | 5 +++++ .../member/application/MemberService.java | 19 +++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java index c87c2f0c4..8ce5c3681 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java @@ -14,6 +14,7 @@ import com.woowacourse.kkogkkog.coupon.domain.CouponStatus; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponHistoryRepository; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponRepository; +import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository; import com.woowacourse.kkogkkog.coupon.exception.CouponNotFoundException; import com.woowacourse.kkogkkog.infrastructure.event.PushAlarmPublisher; import com.woowacourse.kkogkkog.member.domain.Member; @@ -33,15 +34,18 @@ public class CouponService { private final MemberRepository memberRepository; private final CouponRepository couponRepository; private final CouponHistoryRepository couponHistoryRepository; + private final UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository; private final PushAlarmPublisher pushAlarmPublisher; public CouponService(MemberRepository memberRepository, CouponRepository couponRepository, CouponHistoryRepository couponHistoryRepository, + UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository, PushAlarmPublisher pushAlarmPublisher) { this.memberRepository = memberRepository; this.couponRepository = couponRepository; this.couponHistoryRepository = couponHistoryRepository; + this.unreadNoticeCountCacheRepository = unreadNoticeCountCacheRepository; this.pushAlarmPublisher = pushAlarmPublisher; } @@ -131,6 +135,7 @@ private List findReceivers(List memberIds) { private void saveCouponHistory(CouponHistory couponHistory) { couponHistory = couponHistoryRepository.save(couponHistory); + unreadNoticeCountCacheRepository.increment(couponHistory.getHostMember()); // TODO: 복수 쿠폰 생성시 한번만 Redis 접근하도록 수정 pushAlarmPublisher.publishEvent(couponHistory); } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java index 4c73df781..9fc084e50 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java @@ -5,6 +5,7 @@ import com.woowacourse.kkogkkog.auth.application.dto.MemberUpdateResponse; import com.woowacourse.kkogkkog.coupon.domain.CouponHistory; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponHistoryRepository; +import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository; import com.woowacourse.kkogkkog.infrastructure.dto.SlackUserInfo; import com.woowacourse.kkogkkog.member.application.dto.MemberHistoryResponse; import com.woowacourse.kkogkkog.member.application.dto.MemberNicknameUpdateRequest; @@ -30,13 +31,16 @@ public class MemberService { private final MemberRepository memberRepository; private final WorkspaceUserRepository workspaceUserRepository; private final CouponHistoryRepository memberHistoryRepository; + private final UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository; public MemberService(MemberRepository memberRepository, WorkspaceUserRepository workspaceUserRepository, - CouponHistoryRepository couponHistoryRepository) { + CouponHistoryRepository couponHistoryRepository, + UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository) { this.memberRepository = memberRepository; this.workspaceUserRepository = workspaceUserRepository; this.memberHistoryRepository = couponHistoryRepository; + this.unreadNoticeCountCacheRepository = unreadNoticeCountCacheRepository; } public boolean existsMember(SlackUserInfo userInfo) { @@ -95,12 +99,11 @@ private MemberUpdateResponse integrateNewWorkspaceUser(SlackUserInfo userInfo, @Transactional(readOnly = true) public MyProfileResponse findById(Long memberId) { - Member findMember = memberRepository.findById(memberId) + Member member = memberRepository.findById(memberId) .orElseThrow(MemberNotFoundException::new); - Long unreadHistoryCount = memberHistoryRepository - .countByHostMemberAndIsReadFalse(findMember); + Long unreadCount = unreadNoticeCountCacheRepository.get(member); - return MyProfileResponse.of(findMember, unreadHistoryCount); + return MyProfileResponse.of(member, unreadCount); } @Transactional(readOnly = true) @@ -131,7 +134,10 @@ public void updateIsReadMemberHistory(Long memberHistoryId) { CouponHistory memberHistory = memberHistoryRepository.findById(memberHistoryId) .orElseThrow(MemberHistoryNotFoundException::new); - memberHistory.updateIsRead(); + if (!memberHistory.getIsRead()) { + memberHistory.updateIsRead(); + unreadNoticeCountCacheRepository.decrement(memberHistory.getHostMember()); + } } public void updateAllIsReadMemberHistories(Long memberId) { @@ -143,5 +149,6 @@ public void updateAllIsReadMemberHistories(Long memberId) { for (CouponHistory couponHistory : couponHistories) { couponHistory.updateIsRead(); } + unreadNoticeCountCacheRepository.reset(foundMember); } } From c1f074381da3180dce53bf0997439b9c9ff42c87 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 22:21:51 +0900 Subject: [PATCH 07/18] =?UTF-8?q?fix:=20gradle=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/build.gradle b/backend/build.gradle index 1ce60163f..e46aed342 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -50,7 +50,7 @@ dependencies { // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'it.ozimov.embedded-redis:0.7.2' + implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2' asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' From 3ff420314c33451a7b19d3c1fb8653b1cce76d75 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 23:14:13 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20=EB=82=B4=EC=9E=A5=20=EB=A0=88?= =?UTF-8?q?=EB=94=94=EC=8A=A4=20=EA=B5=AC=EB=8F=99=20=EB=B0=8F=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=85=8B=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 및 로컬 환경에서 구동 시, 해당 포트에 구동 중인 서버가 없으면 직접 Redis 서버를 하나 구동 - H2 DB와 동일한 역할 수행 --- .../common/config/EmbeddedRedisConfig.java | 37 +++++++++++++++++++ .../kkogkkog/common/config/RedisConfig.java | 33 +++++++++++++++++ backend/src/main/resources/application.yml | 3 ++ 3 files changed, 73 insertions(+) create mode 100644 backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java create mode 100644 backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java new file mode 100644 index 000000000..55dbc9237 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java @@ -0,0 +1,37 @@ +package com.woowacourse.kkogkkog.common.config; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import redis.embedded.RedisServer; + +@Profile("default") +@Configuration +public class EmbeddedRedisConfig { + + private final int redisPort; + + private RedisServer redisServer; + + public EmbeddedRedisConfig(@Value("${spring.redis.port}") final int redisPort) { + this.redisPort = redisPort; + } + + @PostConstruct + public void redisServer() { + try { + redisServer = new RedisServer(redisPort); + redisServer.start(); + } catch (Exception e) { + } + } + + @PreDestroy + public void stopRedis() { + if (redisServer != null) { + redisServer.stop(); + } + } +} diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java new file mode 100644 index 000000000..1a7c9698d --- /dev/null +++ b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java @@ -0,0 +1,33 @@ +package com.woowacourse.kkogkkog.common.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +public class RedisConfig { + + private final String redisHost; + private final int redisPort; + + public RedisConfig(@Value("${spring.redis.host}") final String redisHost, + @Value("${spring.redis.port}") final int redisPort) { + this.redisHost = redisHost; + this.redisPort = redisPort; + } + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisHost, redisPort); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index f931457a5..1ee36ae4d 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -15,6 +15,9 @@ spring: password: flyway: enabled: false + redis: + host: localhost + port: 6379 logging: level: From dad0a106256afad7b9ee6ec17c84ba202ee2264d Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 23:28:53 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20Redis=EC=97=90=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=EB=90=98=EB=8A=94=20=EC=95=8C=EB=A6=BC=20=EA=B0=9C?= =?UTF-8?q?=EC=88=98=20=EC=BA=90=EC=8B=9C=20=EC=A0=80=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 실행시, Redis의 데이터를 전부 지우기 위한 RedisStorageCleaner 구현 --- .../UnreadNoticeCountCacheRepository2.java | 59 +++++++++++++++++++ .../UnreadNoticeCountRepositoryTest.java | 17 +++--- .../support/common/RedisStorageCleaner.java | 18 ++++++ 3 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository2.java create mode 100644 backend/src/test/java/com/woowacourse/kkogkkog/support/common/RedisStorageCleaner.java diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository2.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository2.java new file mode 100644 index 000000000..e3ab7cae6 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository2.java @@ -0,0 +1,59 @@ +package com.woowacourse.kkogkkog.coupon.domain.repository; + +import com.woowacourse.kkogkkog.member.domain.Member; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Repository; + +@Repository +public class UnreadNoticeCountCacheRepository2 { + + private static final String keyFormat = "unreadNotice:%d"; + + private final RedisTemplate redisTemplate; + private final CouponHistoryRepository couponHistoryRepository; + + public UnreadNoticeCountCacheRepository2(RedisTemplate redisTemplate, + CouponHistoryRepository couponHistoryRepository) { + this.redisTemplate = redisTemplate; + this.couponHistoryRepository = couponHistoryRepository; + } + + public Long get(Member member) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + Long unreadCountCache = valueOperations.get(toCacheKey(member)); + if (unreadCountCache != null) { + return unreadCountCache; + } + Long unreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); + valueOperations.set(toCacheKey(member), unreadCount); + return unreadCount; + } + + public void increment(Member member) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + Long unreadCount = valueOperations.get(toCacheKey(member)); + if (unreadCount == null) { + unreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); + } + valueOperations.set(toCacheKey(member), unreadCount + 1); + } + + public void decrement(Member member) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + Long unreadCount = valueOperations.get(toCacheKey(member)); + if (unreadCount == null) { + unreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); + } + valueOperations.set(toCacheKey(member), Math.max(unreadCount - 1, 0)); + } + + public void reset(Member member) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + valueOperations.set(toCacheKey(member), 0L); + } + + private String toCacheKey(Member member) { + return String.format(keyFormat, member.getId()); + } +} diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java index fc6f57a0d..db32ed1c3 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java @@ -13,30 +13,30 @@ import com.woowacourse.kkogkkog.member.domain.Workspace; import com.woowacourse.kkogkkog.member.domain.repository.MemberRepository; import com.woowacourse.kkogkkog.member.domain.repository.WorkspaceRepository; +import com.woowacourse.kkogkkog.support.application.ApplicationTest; +import com.woowacourse.kkogkkog.support.common.RedisStorageCleaner; import com.woowacourse.kkogkkog.support.fixture.domain.CouponFixture; -import com.woowacourse.kkogkkog.support.repository.RepositoryTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -@RepositoryTest +@ApplicationTest class UnreadNoticeCountRepositoryTest { @Autowired private CouponHistoryRepository couponHistoryRepository; - @Autowired private MemberRepository memberRepository; - @Autowired private WorkspaceRepository workspaceRepository; - @Autowired private CouponRepository couponRepository; - - private UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository; + @Autowired + private UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository; + @Autowired + private RedisStorageCleaner redisStorageCleaner; private Member sender; private Member receiver; @@ -46,8 +46,7 @@ void setUp() { Workspace workspace = workspaceRepository.save(KKOGKKOG.getWorkspace()); sender = memberRepository.save(SENDER.getMember(workspace)); receiver = memberRepository.save(RECEIVER.getMember(workspace)); - unreadNoticeCountCacheRepository = - new UnreadNoticeCountCacheRepository(couponHistoryRepository); + redisStorageCleaner.execute(); } @Nested diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/support/common/RedisStorageCleaner.java b/backend/src/test/java/com/woowacourse/kkogkkog/support/common/RedisStorageCleaner.java new file mode 100644 index 000000000..f6d6d1ad1 --- /dev/null +++ b/backend/src/test/java/com/woowacourse/kkogkkog/support/common/RedisStorageCleaner.java @@ -0,0 +1,18 @@ +package com.woowacourse.kkogkkog.support.common; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Component +public class RedisStorageCleaner { + + @Autowired + private RedisTemplate redisTemplate; + + public void execute() { + RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); + connection.flushDb(); + } +} From b0bd2a4cffa57794fb883b92bb195141eaa10e8a Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 23:30:11 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20HashMap=EC=97=90=EC=84=9C=20Redis?= =?UTF-8?q?=20=EA=B8=B0=EB=B0=98=EC=9D=98=20=EC=BA=90=EC=8B=9C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EB=A1=9C=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kkogkkog/coupon/application/CouponService.java | 6 +++--- .../kkogkkog/member/application/MemberService.java | 6 +++--- .../kkogkkog/acceptance/support/AcceptanceTest.java | 5 +++++ .../kkogkkog/support/application/ServiceTest.java | 5 +++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java index 8ce5c3681..2d6fd65a1 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java @@ -14,7 +14,7 @@ import com.woowacourse.kkogkkog.coupon.domain.CouponStatus; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponHistoryRepository; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponRepository; -import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository; +import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository2; import com.woowacourse.kkogkkog.coupon.exception.CouponNotFoundException; import com.woowacourse.kkogkkog.infrastructure.event.PushAlarmPublisher; import com.woowacourse.kkogkkog.member.domain.Member; @@ -34,13 +34,13 @@ public class CouponService { private final MemberRepository memberRepository; private final CouponRepository couponRepository; private final CouponHistoryRepository couponHistoryRepository; - private final UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository; + private final UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository; private final PushAlarmPublisher pushAlarmPublisher; public CouponService(MemberRepository memberRepository, CouponRepository couponRepository, CouponHistoryRepository couponHistoryRepository, - UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository, + UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository, PushAlarmPublisher pushAlarmPublisher) { this.memberRepository = memberRepository; this.couponRepository = couponRepository; diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java index 9fc084e50..61e33aa67 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java @@ -5,7 +5,7 @@ import com.woowacourse.kkogkkog.auth.application.dto.MemberUpdateResponse; import com.woowacourse.kkogkkog.coupon.domain.CouponHistory; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponHistoryRepository; -import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository; +import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository2; import com.woowacourse.kkogkkog.infrastructure.dto.SlackUserInfo; import com.woowacourse.kkogkkog.member.application.dto.MemberHistoryResponse; import com.woowacourse.kkogkkog.member.application.dto.MemberNicknameUpdateRequest; @@ -31,12 +31,12 @@ public class MemberService { private final MemberRepository memberRepository; private final WorkspaceUserRepository workspaceUserRepository; private final CouponHistoryRepository memberHistoryRepository; - private final UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository; + private final UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository; public MemberService(MemberRepository memberRepository, WorkspaceUserRepository workspaceUserRepository, CouponHistoryRepository couponHistoryRepository, - UnreadNoticeCountCacheRepository unreadNoticeCountCacheRepository) { + UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository) { this.memberRepository = memberRepository; this.workspaceUserRepository = workspaceUserRepository; this.memberHistoryRepository = couponHistoryRepository; diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/acceptance/support/AcceptanceTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/acceptance/support/AcceptanceTest.java index 7ac29815b..a658ef20d 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/acceptance/support/AcceptanceTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/acceptance/support/AcceptanceTest.java @@ -2,6 +2,7 @@ import com.woowacourse.kkogkkog.infrastructure.application.SlackClient; import com.woowacourse.kkogkkog.support.common.DatabaseCleaner; +import com.woowacourse.kkogkkog.support.common.RedisStorageCleaner; import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; @@ -19,6 +20,9 @@ public class AcceptanceTest { @Autowired private DatabaseCleaner databaseCleaner; + @Autowired + private RedisStorageCleaner redisStorageCleaner; + @MockBean protected static SlackClient slackClient; @@ -26,5 +30,6 @@ public class AcceptanceTest { void setUp() { RestAssured.port = port; databaseCleaner.execute(); + redisStorageCleaner.execute(); } } diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/support/application/ServiceTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/support/application/ServiceTest.java index 81417bf80..64dcdc3df 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/support/application/ServiceTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/support/application/ServiceTest.java @@ -2,6 +2,7 @@ import com.woowacourse.kkogkkog.infrastructure.application.SlackClient; import com.woowacourse.kkogkkog.support.common.DatabaseCleaner; +import com.woowacourse.kkogkkog.support.common.RedisStorageCleaner; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -13,11 +14,15 @@ public class ServiceTest { @Autowired private DatabaseCleaner databaseCleaner; + @Autowired + private RedisStorageCleaner redisStorageCleaner; + @MockBean protected SlackClient slackClient; @BeforeEach void setUp() { databaseCleaner.execute(); + redisStorageCleaner.execute(); } } From 8a3b26ee2c3ee023108a090fc2209b4be7644263 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Thu, 29 Sep 2022 23:31:45 +0900 Subject: [PATCH 11/18] =?UTF-8?q?refactor:=20NoticeCacheRepository=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coupon/application/CouponService.java | 10 ++--- ...itory2.java => NoticeCacheRepository.java} | 8 ++-- .../UnreadNoticeCountCacheRepository.java | 44 ------------------- .../member/application/MemberService.java | 14 +++--- .../UnreadNoticeCountRepositoryTest.java | 36 +++++++-------- 5 files changed, 34 insertions(+), 78 deletions(-) rename backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/{UnreadNoticeCountCacheRepository2.java => NoticeCacheRepository.java} (87%) delete mode 100644 backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java index 2d6fd65a1..605afa975 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java @@ -14,7 +14,7 @@ import com.woowacourse.kkogkkog.coupon.domain.CouponStatus; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponHistoryRepository; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponRepository; -import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository2; +import com.woowacourse.kkogkkog.coupon.domain.repository.NoticeCacheRepository; import com.woowacourse.kkogkkog.coupon.exception.CouponNotFoundException; import com.woowacourse.kkogkkog.infrastructure.event.PushAlarmPublisher; import com.woowacourse.kkogkkog.member.domain.Member; @@ -34,18 +34,18 @@ public class CouponService { private final MemberRepository memberRepository; private final CouponRepository couponRepository; private final CouponHistoryRepository couponHistoryRepository; - private final UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository; + private final NoticeCacheRepository noticeCacheRepository; private final PushAlarmPublisher pushAlarmPublisher; public CouponService(MemberRepository memberRepository, CouponRepository couponRepository, CouponHistoryRepository couponHistoryRepository, - UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository, + NoticeCacheRepository noticeCacheRepository, PushAlarmPublisher pushAlarmPublisher) { this.memberRepository = memberRepository; this.couponRepository = couponRepository; this.couponHistoryRepository = couponHistoryRepository; - this.unreadNoticeCountCacheRepository = unreadNoticeCountCacheRepository; + this.noticeCacheRepository = noticeCacheRepository; this.pushAlarmPublisher = pushAlarmPublisher; } @@ -135,7 +135,7 @@ private List findReceivers(List memberIds) { private void saveCouponHistory(CouponHistory couponHistory) { couponHistory = couponHistoryRepository.save(couponHistory); - unreadNoticeCountCacheRepository.increment(couponHistory.getHostMember()); // TODO: 복수 쿠폰 생성시 한번만 Redis 접근하도록 수정 + noticeCacheRepository.increment(couponHistory.getHostMember()); pushAlarmPublisher.publishEvent(couponHistory); } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository2.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java similarity index 87% rename from backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository2.java rename to backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java index e3ab7cae6..6403e386f 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository2.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java @@ -6,15 +6,15 @@ import org.springframework.stereotype.Repository; @Repository -public class UnreadNoticeCountCacheRepository2 { +public class NoticeCacheRepository { - private static final String keyFormat = "unreadNotice:%d"; + private static final String keyFormat = "unreadNoticeCount:%d"; private final RedisTemplate redisTemplate; private final CouponHistoryRepository couponHistoryRepository; - public UnreadNoticeCountCacheRepository2(RedisTemplate redisTemplate, - CouponHistoryRepository couponHistoryRepository) { + public NoticeCacheRepository(RedisTemplate redisTemplate, + CouponHistoryRepository couponHistoryRepository) { this.redisTemplate = redisTemplate; this.couponHistoryRepository = couponHistoryRepository; } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java deleted file mode 100644 index f88a40261..000000000 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountCacheRepository.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.woowacourse.kkogkkog.coupon.domain.repository; - -import com.woowacourse.kkogkkog.member.domain.Member; -import java.util.HashMap; -import org.springframework.stereotype.Repository; - -@Repository -public class UnreadNoticeCountCacheRepository { - - private final HashMap unreadCountCache = new HashMap<>(); - private final CouponHistoryRepository couponHistoryRepository; - - public UnreadNoticeCountCacheRepository(CouponHistoryRepository couponHistoryRepository) { - this.couponHistoryRepository = couponHistoryRepository; - } - - public Long get(Member member) { - Long memberId = member.getId(); - if (!unreadCountCache.containsKey(memberId)) { - updateCache(member); - } - return unreadCountCache.get(memberId); - } - - private void updateCache(Member member) { - Long validUnreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); - unreadCountCache.put(member.getId(), validUnreadCount); - } - - public void increment(Member member) { - unreadCountCache.put(member.getId(), get(member) + 1); - } - - public void decrement(Member member) { - Long currentUpdateCount = get(member); - if (currentUpdateCount > 0) { - unreadCountCache.put(member.getId(), currentUpdateCount - 1); - } - } - - public void reset(Member member) { - unreadCountCache.put(member.getId(), 0L); - } -} diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java index 61e33aa67..12211917e 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java @@ -5,7 +5,7 @@ import com.woowacourse.kkogkkog.auth.application.dto.MemberUpdateResponse; import com.woowacourse.kkogkkog.coupon.domain.CouponHistory; import com.woowacourse.kkogkkog.coupon.domain.repository.CouponHistoryRepository; -import com.woowacourse.kkogkkog.coupon.domain.repository.UnreadNoticeCountCacheRepository2; +import com.woowacourse.kkogkkog.coupon.domain.repository.NoticeCacheRepository; import com.woowacourse.kkogkkog.infrastructure.dto.SlackUserInfo; import com.woowacourse.kkogkkog.member.application.dto.MemberHistoryResponse; import com.woowacourse.kkogkkog.member.application.dto.MemberNicknameUpdateRequest; @@ -31,16 +31,16 @@ public class MemberService { private final MemberRepository memberRepository; private final WorkspaceUserRepository workspaceUserRepository; private final CouponHistoryRepository memberHistoryRepository; - private final UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository; + private final NoticeCacheRepository noticeCacheRepository; public MemberService(MemberRepository memberRepository, WorkspaceUserRepository workspaceUserRepository, CouponHistoryRepository couponHistoryRepository, - UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository) { + NoticeCacheRepository noticeCacheRepository) { this.memberRepository = memberRepository; this.workspaceUserRepository = workspaceUserRepository; this.memberHistoryRepository = couponHistoryRepository; - this.unreadNoticeCountCacheRepository = unreadNoticeCountCacheRepository; + this.noticeCacheRepository = noticeCacheRepository; } public boolean existsMember(SlackUserInfo userInfo) { @@ -101,7 +101,7 @@ private MemberUpdateResponse integrateNewWorkspaceUser(SlackUserInfo userInfo, public MyProfileResponse findById(Long memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(MemberNotFoundException::new); - Long unreadCount = unreadNoticeCountCacheRepository.get(member); + Long unreadCount = noticeCacheRepository.get(member); return MyProfileResponse.of(member, unreadCount); } @@ -136,7 +136,7 @@ public void updateIsReadMemberHistory(Long memberHistoryId) { if (!memberHistory.getIsRead()) { memberHistory.updateIsRead(); - unreadNoticeCountCacheRepository.decrement(memberHistory.getHostMember()); + noticeCacheRepository.decrement(memberHistory.getHostMember()); } } @@ -149,6 +149,6 @@ public void updateAllIsReadMemberHistories(Long memberId) { for (CouponHistory couponHistory : couponHistories) { couponHistory.updateIsRead(); } - unreadNoticeCountCacheRepository.reset(foundMember); + noticeCacheRepository.reset(foundMember); } } diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java index db32ed1c3..62582a60a 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java @@ -34,7 +34,7 @@ class UnreadNoticeCountRepositoryTest { @Autowired private CouponRepository couponRepository; @Autowired - private UnreadNoticeCountCacheRepository2 unreadNoticeCountCacheRepository; + private NoticeCacheRepository noticeCacheRepository; @Autowired private RedisStorageCleaner redisStorageCleaner; @@ -61,7 +61,7 @@ void updateAndReadCache() { couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); - Long unreadCount = unreadNoticeCountCacheRepository.get(receiver); + Long unreadCount = noticeCacheRepository.get(receiver); assertThat(unreadCount).isEqualTo(2); } @@ -73,8 +73,8 @@ void readExistingCache() { couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); - Long actual = unreadNoticeCountCacheRepository.get(receiver); - Long expected = unreadNoticeCountCacheRepository.get(receiver); + Long actual = noticeCacheRepository.get(receiver); + Long expected = noticeCacheRepository.get(receiver); assertThat(actual).isEqualTo(expected); } @@ -87,10 +87,10 @@ class Increment { @Test @DisplayName("캐쉬 데이터의 값을 1만큼 증가시킨다.") void readAndUpdate() { - Long previousCount = unreadNoticeCountCacheRepository.get(receiver); + Long previousCount = noticeCacheRepository.get(receiver); - unreadNoticeCountCacheRepository.increment(receiver); - Long actual = unreadNoticeCountCacheRepository.get(receiver); + noticeCacheRepository.increment(receiver); + Long actual = noticeCacheRepository.get(receiver); assertThat(actual).isEqualTo(previousCount + 1); } @@ -107,10 +107,10 @@ void readAndUpdate() { couponHistoryRepository.save(INIT.getCouponHistory(sender, coupon)); couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); - Long previousCount = unreadNoticeCountCacheRepository.get(receiver); + Long previousCount = noticeCacheRepository.get(receiver); - unreadNoticeCountCacheRepository.decrement(receiver); - Long actual = unreadNoticeCountCacheRepository.get(receiver); + noticeCacheRepository.decrement(receiver); + Long actual = noticeCacheRepository.get(receiver); assertThat(actual).isEqualTo(previousCount - 1); } @@ -118,10 +118,10 @@ void readAndUpdate() { @Test @DisplayName("캐쉬 데이터의 값이 0인 경우 감소시키지 않는다.") void minValueZero() { - Long previousCount = unreadNoticeCountCacheRepository.get(receiver); + Long previousCount = noticeCacheRepository.get(receiver); - unreadNoticeCountCacheRepository.decrement(receiver); - Long actual = unreadNoticeCountCacheRepository.get(receiver); + noticeCacheRepository.decrement(receiver); + Long actual = noticeCacheRepository.get(receiver); assertThat(actual).isEqualTo(previousCount); } @@ -138,10 +138,10 @@ void resetToZero() { couponHistoryRepository.save(INIT.getCouponHistory(sender, coupon)); couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); - unreadNoticeCountCacheRepository.get(receiver); + noticeCacheRepository.get(receiver); - unreadNoticeCountCacheRepository.reset(receiver); - Long actual = unreadNoticeCountCacheRepository.get(receiver); + noticeCacheRepository.reset(receiver); + Long actual = noticeCacheRepository.get(receiver); assertThat(actual).isEqualTo(0); } @@ -149,8 +149,8 @@ void resetToZero() { @Test @DisplayName("캐쉬 데이터가 없는 경우 0으로 초기화한다.") void initWithZero() { - unreadNoticeCountCacheRepository.reset(receiver); - Long actual = unreadNoticeCountCacheRepository.get(receiver); + noticeCacheRepository.reset(receiver); + Long actual = noticeCacheRepository.get(receiver); assertThat(actual).isEqualTo(0); } From 1ac55c3918a6953770c58f40c3596bca83f6175f Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Fri, 30 Sep 2022 00:05:28 +0900 Subject: [PATCH 12/18] =?UTF-8?q?fix:=20=EC=BA=90=EC=8B=9C=EA=B0=80=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=EC=97=90=20=EC=97=86=EB=8D=98=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=98=AC=EB=B0=94=EB=A5=B8=20=EA=B0=92=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=83=9D=EC=84=B1=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowacourse/kkogkkog/coupon/application/CouponService.java | 2 +- .../woowacourse/kkogkkog/member/application/MemberService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java index 605afa975..cedc03ff7 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java @@ -134,8 +134,8 @@ private List findReceivers(List memberIds) { } private void saveCouponHistory(CouponHistory couponHistory) { - couponHistory = couponHistoryRepository.save(couponHistory); noticeCacheRepository.increment(couponHistory.getHostMember()); + couponHistory = couponHistoryRepository.save(couponHistory); pushAlarmPublisher.publishEvent(couponHistory); } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java index 12211917e..33b4a3638 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java @@ -135,8 +135,8 @@ public void updateIsReadMemberHistory(Long memberHistoryId) { .orElseThrow(MemberHistoryNotFoundException::new); if (!memberHistory.getIsRead()) { - memberHistory.updateIsRead(); noticeCacheRepository.decrement(memberHistory.getHostMember()); + memberHistory.updateIsRead(); } } From 33e89e3481b40665144417364ca003c3fed66456 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Fri, 30 Sep 2022 00:19:55 +0900 Subject: [PATCH 13/18] =?UTF-8?q?feat:=20=EC=BA=90=EC=8B=9C=EA=B0=80=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EA=B5=B3=EC=9D=B4=20=EA=B0=B1=EC=8B=A0?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 데이터 조회 시점에 실제 데이터를 기반으로 캐시를 생성하는 것이 버그의 가능성을 줄이며, 응답 성능을 덜 저하시킴. --- .../coupon/application/CouponService.java | 2 +- .../repository/NoticeCacheRepository.java | 14 +++++----- .../member/application/MemberService.java | 2 +- .../UnreadNoticeCountRepositoryTest.java | 26 +++++++++++++++++++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java index cedc03ff7..605afa975 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/application/CouponService.java @@ -134,8 +134,8 @@ private List findReceivers(List memberIds) { } private void saveCouponHistory(CouponHistory couponHistory) { - noticeCacheRepository.increment(couponHistory.getHostMember()); couponHistory = couponHistoryRepository.save(couponHistory); + noticeCacheRepository.increment(couponHistory.getHostMember()); pushAlarmPublisher.publishEvent(couponHistory); } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java index 6403e386f..5915181ee 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java @@ -1,6 +1,7 @@ package com.woowacourse.kkogkkog.coupon.domain.repository; import com.woowacourse.kkogkkog.member.domain.Member; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Repository; @@ -9,6 +10,7 @@ public class NoticeCacheRepository { private static final String keyFormat = "unreadNoticeCount:%d"; + private static final int CACHE_VALIDITY = 600; private final RedisTemplate redisTemplate; private final CouponHistoryRepository couponHistoryRepository; @@ -26,26 +28,24 @@ public Long get(Member member) { return unreadCountCache; } Long unreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); - valueOperations.set(toCacheKey(member), unreadCount); + valueOperations.set(toCacheKey(member), unreadCount, CACHE_VALIDITY, TimeUnit.SECONDS); return unreadCount; } public void increment(Member member) { ValueOperations valueOperations = redisTemplate.opsForValue(); Long unreadCount = valueOperations.get(toCacheKey(member)); - if (unreadCount == null) { - unreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); + if (unreadCount != null) { + valueOperations.set(toCacheKey(member), unreadCount + 1); } - valueOperations.set(toCacheKey(member), unreadCount + 1); } public void decrement(Member member) { ValueOperations valueOperations = redisTemplate.opsForValue(); Long unreadCount = valueOperations.get(toCacheKey(member)); - if (unreadCount == null) { - unreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); + if (unreadCount != null && unreadCount > 0) { + valueOperations.set(toCacheKey(member), unreadCount - 1); } - valueOperations.set(toCacheKey(member), Math.max(unreadCount - 1, 0)); } public void reset(Member member) { diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java index 33b4a3638..12211917e 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java @@ -135,8 +135,8 @@ public void updateIsReadMemberHistory(Long memberHistoryId) { .orElseThrow(MemberHistoryNotFoundException::new); if (!memberHistory.getIsRead()) { - noticeCacheRepository.decrement(memberHistory.getHostMember()); memberHistory.updateIsRead(); + noticeCacheRepository.decrement(memberHistory.getHostMember()); } } diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java index 62582a60a..8c2100c86 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java @@ -94,6 +94,17 @@ void readAndUpdate() { assertThat(actual).isEqualTo(previousCount + 1); } + + @Test + @DisplayName("캐쉬 데이터가 존재하지 않는 경우 굳이 생성 및 반영하지 않는다.") + void noUpdateOnNoCache() { + noticeCacheRepository.increment(receiver); + noticeCacheRepository.increment(receiver); + noticeCacheRepository.increment(receiver); + Long actual = noticeCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(0); + } } @Nested @@ -125,6 +136,21 @@ void minValueZero() { assertThat(actual).isEqualTo(previousCount); } + + @Test + @DisplayName("캐쉬 데이터가 존재하지 않는 경우 굳이 생성 및 반영하지 않는다.") + void noUpdateOnNoCache() { + Coupon coupon = couponRepository.save(CouponFixture.COFFEE.getCoupon(sender, receiver)); + couponHistoryRepository.save(INIT.getCouponHistory(sender, coupon)); + couponHistoryRepository.save(REQUEST.getCouponHistory(receiver, coupon)); + couponHistoryRepository.save(DECLINE.getCouponHistory(sender, coupon)); + + noticeCacheRepository.decrement(receiver); + noticeCacheRepository.decrement(receiver); + Long actual = noticeCacheRepository.get(receiver); + + assertThat(actual).isEqualTo(2); + } } @Nested From f5ebd948bae50bcab3aa2b8306f932bc19a9dbcc Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Fri, 30 Sep 2022 00:25:42 +0900 Subject: [PATCH 14/18] =?UTF-8?q?refactor:=20private=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B8=B0=EB=B0=98=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/NoticeCacheRepository.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java index 5915181ee..cbbc3a357 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java @@ -22,38 +22,42 @@ public NoticeCacheRepository(RedisTemplate redisTemplate, } public Long get(Member member) { - ValueOperations valueOperations = redisTemplate.opsForValue(); - Long unreadCountCache = valueOperations.get(toCacheKey(member)); + Long unreadCountCache = getCache(member); if (unreadCountCache != null) { return unreadCountCache; } Long unreadCount = couponHistoryRepository.countByHostMemberAndIsReadFalse(member); - valueOperations.set(toCacheKey(member), unreadCount, CACHE_VALIDITY, TimeUnit.SECONDS); + setCache(member, unreadCount); return unreadCount; } public void increment(Member member) { - ValueOperations valueOperations = redisTemplate.opsForValue(); - Long unreadCount = valueOperations.get(toCacheKey(member)); + Long unreadCount = getCache(member); if (unreadCount != null) { - valueOperations.set(toCacheKey(member), unreadCount + 1); + setCache(member, unreadCount + 1); } } public void decrement(Member member) { - ValueOperations valueOperations = redisTemplate.opsForValue(); - Long unreadCount = valueOperations.get(toCacheKey(member)); + Long unreadCount = getCache(member); if (unreadCount != null && unreadCount > 0) { - valueOperations.set(toCacheKey(member), unreadCount - 1); + setCache(member, unreadCount - 1); } } public void reset(Member member) { + setCache(member, 0L); + } + + private Long getCache(Member member) { ValueOperations valueOperations = redisTemplate.opsForValue(); - valueOperations.set(toCacheKey(member), 0L); + String cacheKey = String.format(keyFormat, member.getId()); + return valueOperations.get(cacheKey); } - private String toCacheKey(Member member) { - return String.format(keyFormat, member.getId()); + private void setCache(Member member, Long unreadCount) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String cacheKey = String.format(keyFormat, member.getId()); + valueOperations.set(cacheKey, unreadCount, CACHE_VALIDITY, TimeUnit.SECONDS); } } From b85caa5b9f9f5daf616267182df9b78346107e49 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Fri, 30 Sep 2022 13:41:19 +0900 Subject: [PATCH 15/18] =?UTF-8?q?refactor:=20SonarCloud=EC=9D=98=20code=20?= =?UTF-8?q?smell=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kkogkkog/common/config/EmbeddedRedisConfig.java | 5 ++++- .../woowacourse/kkogkkog/coupon/domain/CouponHistory.java | 7 ++++++- .../coupon/domain/repository/NoticeCacheRepository.java | 6 +++--- .../kkogkkog/member/application/MemberService.java | 2 +- .../domain/repository/UnreadNoticeCountRepositoryTest.java | 6 +++--- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java index 55dbc9237..5b6f0221e 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/EmbeddedRedisConfig.java @@ -2,11 +2,13 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import redis.embedded.RedisServer; +@Slf4j @Profile("default") @Configuration public class EmbeddedRedisConfig { @@ -24,7 +26,8 @@ public void redisServer() { try { redisServer = new RedisServer(redisPort); redisServer.start(); - } catch (Exception e) { + } catch (RuntimeException e) { + log.info("{} 포트에 내장 레디스 서버를 구동하는 데 실패하였습니다.", redisPort); } } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/CouponHistory.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/CouponHistory.java index b6d2ed10e..6c1fd97ad 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/CouponHistory.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/CouponHistory.java @@ -81,11 +81,16 @@ public static CouponHistory ofNew(Coupon coupon) { return new CouponHistory(historyHostMember, coupon.getSender(), coupon, initEvent, null); } - public static CouponHistory of(Member loginMember, Coupon coupon, CouponEvent event, String message) { + public static CouponHistory of(Member loginMember, Coupon coupon, CouponEvent event, + String message) { Member historyHostMember = coupon.getOppositeMember(loginMember); return new CouponHistory(historyHostMember, loginMember, coupon, event, message); } + public boolean isRead() { + return isRead; + } + public void updateIsRead() { isRead = true; } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java index cbbc3a357..95dbfd91a 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java @@ -9,7 +9,7 @@ @Repository public class NoticeCacheRepository { - private static final String keyFormat = "unreadNoticeCount:%d"; + private static final String KEY_FORMAT = "unreadNoticeCount:%d"; private static final int CACHE_VALIDITY = 600; private final RedisTemplate redisTemplate; @@ -51,13 +51,13 @@ public void reset(Member member) { private Long getCache(Member member) { ValueOperations valueOperations = redisTemplate.opsForValue(); - String cacheKey = String.format(keyFormat, member.getId()); + String cacheKey = String.format(KEY_FORMAT, member.getId()); return valueOperations.get(cacheKey); } private void setCache(Member member, Long unreadCount) { ValueOperations valueOperations = redisTemplate.opsForValue(); - String cacheKey = String.format(keyFormat, member.getId()); + String cacheKey = String.format(KEY_FORMAT, member.getId()); valueOperations.set(cacheKey, unreadCount, CACHE_VALIDITY, TimeUnit.SECONDS); } } diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java index 12211917e..e84062937 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/member/application/MemberService.java @@ -134,7 +134,7 @@ public void updateIsReadMemberHistory(Long memberHistoryId) { CouponHistory memberHistory = memberHistoryRepository.findById(memberHistoryId) .orElseThrow(MemberHistoryNotFoundException::new); - if (!memberHistory.getIsRead()) { + if (!memberHistory.isRead()) { memberHistory.updateIsRead(); noticeCacheRepository.decrement(memberHistory.getHostMember()); } diff --git a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java index 8c2100c86..97d26365f 100644 --- a/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/kkogkkog/coupon/domain/repository/UnreadNoticeCountRepositoryTest.java @@ -103,7 +103,7 @@ void noUpdateOnNoCache() { noticeCacheRepository.increment(receiver); Long actual = noticeCacheRepository.get(receiver); - assertThat(actual).isEqualTo(0); + assertThat(actual).isZero(); } } @@ -169,7 +169,7 @@ void resetToZero() { noticeCacheRepository.reset(receiver); Long actual = noticeCacheRepository.get(receiver); - assertThat(actual).isEqualTo(0); + assertThat(actual).isZero(); } @Test @@ -178,7 +178,7 @@ void initWithZero() { noticeCacheRepository.reset(receiver); Long actual = noticeCacheRepository.get(receiver); - assertThat(actual).isEqualTo(0); + assertThat(actual).isZero(); } } } From 9cc3ea353480953110332aebcfb5c782ea54f291 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Fri, 30 Sep 2022 20:40:52 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20DBMS=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=EC=97=90=20=EB=8C=80=ED=95=B4=20@Transaction?= =?UTF-8?q?al=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coupon/domain/repository/NoticeCacheRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java index 95dbfd91a..7dcc3f62a 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/coupon/domain/repository/NoticeCacheRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; @Repository public class NoticeCacheRepository { @@ -21,6 +22,7 @@ public NoticeCacheRepository(RedisTemplate redisTemplate, this.couponHistoryRepository = couponHistoryRepository; } + @Transactional(readOnly = true) public Long get(Member member) { Long unreadCountCache = getCache(member); if (unreadCountCache != null) { From 92d308c414cb41be03581ce0f695e51153ff8e0a Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Tue, 11 Oct 2022 20:31:30 +0900 Subject: [PATCH 17/18] =?UTF-8?q?feat:=20Redis=20=EC=97=B0=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kkogkkog/common/config/RedisConfig.java | 10 ++++++++-- backend/src/main/resources/application.yml | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java index 1a7c9698d..2dc51766e 100644 --- a/backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java +++ b/backend/src/main/java/com/woowacourse/kkogkkog/common/config/RedisConfig.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @@ -12,16 +13,21 @@ public class RedisConfig { private final String redisHost; private final int redisPort; + private final String redisPassword; public RedisConfig(@Value("${spring.redis.host}") final String redisHost, - @Value("${spring.redis.port}") final int redisPort) { + @Value("${spring.redis.port}") final int redisPort, + @Value("${spring.redis.password}") final String redisPassword) { this.redisHost = redisHost; this.redisPort = redisPort; + this.redisPassword = redisPassword; } @Bean public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(redisHost, redisPort); + final var redisConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort); + redisConfiguration.setPassword(redisPassword); + return new LettuceConnectionFactory(redisConfiguration); } @Bean diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 1ee36ae4d..1b357e9f2 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -18,6 +18,7 @@ spring: redis: host: localhost port: 6379 + password: "" logging: level: From fcab300b8681f71cd5d8a3b277d3b5a5af28ca48 Mon Sep 17 00:00:00 2001 From: Jeong Jinwoo Date: Tue, 11 Oct 2022 20:32:16 +0900 Subject: [PATCH 18/18] =?UTF-8?q?chore:=20dev,=20prod=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=B3=84=20Redis=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/backend-secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/backend-secret b/backend/src/main/resources/backend-secret index befb30f10..71cce6c7e 160000 --- a/backend/src/main/resources/backend-secret +++ b/backend/src/main/resources/backend-secret @@ -1 +1 @@ -Subproject commit befb30f10f51eef03e9ba1a6b2c3a393c4d25420 +Subproject commit 71cce6c7e77de4cbb8f3ab8ebb8b3b34633c829e