Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1단계 - DB 복제와 캐시] 폴라(장유진) 미션 제출합니다. #75

Open
wants to merge 16 commits into
base: jinchiim
Choose a base branch
from

Conversation

jinchiim
Copy link
Member

@jinchiim jinchiim commented Oct 18, 2024

안녕하세요 리니! 폴라입니다! 😊

일단 제 가정이 어떤 상황이었는지 말씀드려야 할 것 같아요!

제가 생각한 상황

요구사항에 나와있는 것처럼 쿠폰과 유저쿠폰은 나뉘어져있는 것으로 보아 쿠폰 발급이라는 로직은,
어드민 페이지에서 쓰일 것이라고 생각했습니다.

즉, 예를 들어 크리스마스에 쿠폰을 발급한다고 가정했을 때 이 행위는 당장 발급 1초전, 하루 전날에 이루어지지 않을 것이라고 생각했습니다. 🤔

생각의 흐름

🔥 쿠폰 등록 이후의 복제 지연이 문제인가?

솔직히 문제라고 느껴지지 않았습니다.

보통 쿠폰을 등록한 이후 쿠폰을 다시 본다고 하더라도 1초 이상은 걸릴 것 같았던 것이 첫번째 이유이고
만약 1초라는 수치가 2초가 되더라도 쿠폰 등록 에서 만큼은 문제가 없다고 느꼈습니다.

등록하고 바로 수정을 한다고 하더라도 그 과정이 그만큼 짧은 시간안에 이루어질까요?
쿠폰을 다운받도록 열려있는 상태가 아닌 쿠폰을 만드는 과정이니 사실 1초 아니 2초가 걸려도 문제가 없다고 생각해요.

그래서 솔직히 그냥 둘까 라는 것도 고민에 들어가있었어요.

📑 굳이 다시 읽어야 하는가?

그래도 지연은 지연이니깐요. 정말 정말 만약에 중요한 이벤트여서 모두가 보고있는 시점이라면 다같이 1초를 기다리게 되겠지요.
그래서 두번째로 생각한 것은 그냥 되돌려주기였어요.

저 였다면 쿠폰을 등록했을 때 등록한 쿠폰 정보를 보여주고 뒤로갔을 때 쿠폰 리스트를 조회하게 할 것 같거든요. 🤔
그렇다면 뒤로가기에 쿠폰을 등록하고 뒤로가서 리스트를 불러오는 것은 적어도 1초 이상이 아닐까요?

쿠폰 정보를 보여주는 탭이 없더라도 반환된 쿠폰 정보를 현재 있는 리스트 맨 상단에 붙여주세요 라는
소통만 되어도 문제가 없다고 생각했습니다.

💧 아무도 믿지 말자

그렇지만 이런 경우에는 "소통"이 되어야 한다는게 문제가 될 수 있습니다. 💨
결국 둘 중 한쪽만 삐끗해도 문제가 발생한다는 것이니깐요.

저도 간혹 이렇게 싱크가 안맞았던..그러니까 누군가에겐 당연했지만 당연하지 않았던 상황들이 많아서 이왕이면 소통을 하지 않고도 쓸 수 있게 하는 것이 맞겠다 생각했어요.

또 서버 상에서 이제는 "복제지연 해결했습니다~ 이제 반환 안할거예요~ " 라고 한다면 프론트도 그에 맞는 조치를 취해야겠죠.
이게 굉장히 불편한 것 같아요.

🌟 캐시..?

그 다음으로 생각한 것이 캐싱이였습니다. 왜냐면 이미 RedisDocker-compose 안에 넣어주신 상태였더라구요 ㅋㅋㅋㅋㅋ

그렇지만 결국 선택하지 않았습니다.
일단 Redis가 제공되�긴 했으나 여기서 사용한다고 하더라도
굳이 Redis 를 사용해야만 했던 이유에 대해서는 제가 답할 수 없을 것 같았어요.

데이터 구조가 유연하고 읽기,저장이 빠르다 는 것이 제가 생각하는 Redis의 장점인데 이게 지금 상황에 어울리는가?
로 생각하면 아니라고 생각했습니다.

그래서 일단은 단순히 캐싱만 하기 좋은 데이터베이스나 구조가 뭐가 있을까 좀 더 고민해보는 것으로 결정했습니다.

쿠폰 등록 이후의 복제 지연이 문제인가? 라는 생각을 한 이유

그렇지만 일단 지연 안되게 해주세요 라는 요청이 들어왔다고 가정한다면 기간안에(미션 기간) 지연은 안되게 해야겠죠

다시 처음으로 돌아가서 제가 "쿠폰 등록 이후의 복제 지연이 문제인가?" 라는 생각을 한 이유는 결국

  1. 그만큼 잦은 요청이 들어올 만하지 않은 기능이기 때문에
  2. 1초 단위가 시급한 기능이 아니기 때문에

이 두가지로 좁혀진다고 봤을때 한가지 들어온 생각이 지금의 방식인 Write 에서 읽는 것이였습니다.

🤔 이유

일단 Write 로 읽음으로써 생겼던 걱정은 이랬습니다.

1. 잦다면 `Write` 에 부하가 커질것이라는 점
2. 존재하지 않는 `id` 일때도 `Read` 와 `Write` DB를 모두 거쳐야 한다는 점
3. 만들어진 이후에 바로 수정을 하는 경우 누군가는 다른 정보를 바라보고 있을 것이라는 점

1초라는 전제가 쥐어졌기 때문에 일단 3번은 이뤄지기 힘들다고 보는게 맞을 것 같습니다.
어드민 "페이지" 니까 결국 누군가 등록하고 그게 누군가에게 보여지고, 그것을 누군가가 수정하고, 수정한 것이 등록되고 의 흐름이니깐요.

그리고 1번은 "어드민 페이지" 라는 가정을 했을때
갑자기 저희 회사가 미쳐서 연속적으로 새로운 이벤트용 쿠폰을 생산하자 하는게 아니고서야 "잦지 않다"는 것이 자명하기 때문에 똑같이 힘들다고 볼 수 있을 것 같습니다.

나머지 2번이 제일 마음에 걸렸습니다.
존재하지 않는 id를 호출할일이 얼마나 잦겠냐만은 그럼에도 앞으로 생기는 모든 API 에 없는경우에서 찾아 를 가정하고 코드를 짜야 한다는게 참 불편하고 슬프더라고요. (이건 캐싱도 마찬가지긴 하겠네요)

그래서 2단계에서는 이 부분을 해소하는 것을 목표로 잡고
아직은 잦지 않기 때문에 요구사항이었던 복제 지연 문제는 이렇게 해결하고 2단계 넘어가면서 좀 더 고민해보려고 합니다! 😀

리니의 생각은 어떨지 궁금하네요! 😊

(cherry picked from commit d747e90)
- 공통 Exception 객체 생성
- Error 메시지 상수를 담는 ErrorConstant 생성

(cherry picked from commit a9dca5f)
- 할인 금액에 대한 검증을 적절히 하는지 테스트 작성

(cherry picked from commit fd5cafc)
- 검증을 적절히 하는지 테스트 작성

(cherry picked from commit d4f17f8)
- 검증을 적절히 하는지 테스트 작성

(cherry picked from commit 84553e7)
- 검증을 적절히 하는지 테스트 작성

(cherry picked from commit feb93bb)
- 적절한 검증을 하는지 테스트 작성

(cherry picked from commit 3c75064)
- 적절한 검증을 하는지 테스트 작성

(cherry picked from commit 91f0dfa)
- 회원 엔티티와 매핑되는 도메인 생성

(cherry picked from commit 454fcdb)
- 쿠폰 엔티티와 매핑되는 도메인 생성

(cherry picked from commit d2e595c)
- 데이터베이스가 없을 시 생성하도록 url 수정
- init 테이블 생성
- init 파일을 불러오도록 docker-compose 파일 수정

(cherry picked from commit 57bb7b2)
- 딜레이가 생겨도 적절히 가져오는지 테스트
- 해당되는 값이 없는 경우 에러를 발생시키는지 테스트 작성

(cherry picked from commit eee0155)
Copy link

@linirini linirini left a comment

Choose a reason for hiding this comment

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

폴라~ 안녕하세요🖐️
폴라와 코드로 대화를 하는 것은 처음인 것 같아요😮 감회가 새롭네요 ㅎㅎ
구현해주신 코드와 설명 잘 읽었습니다! 저와 비슷하게 고민을 하신 것 같아요 ㅎㅎ 생각의 흐름을 잘 정리해주시기도 해서 이해가 잘 되었습니다:)
저와는 다른 스타일의 폴라 코드를 보면서 궁금한 점이 많아져서 어쩌다보니 코드 의도 관련 질문이 많아졌어요 ㅎㅎ..
제가 인프라 지식이 얕기도 해서,, 복제 지연 관련해서는 많은 피드백을 못 드린 것 같아 죄송하네요😅 제가 더 많이 배워가는 미션이 될 것만 같은 느낌이 듭니다 허허..
이번 미션 동안 잘 부탁드려요:)

import org.springframework.http.HttpStatus;

@Getter
public class CouponNotFoundException extends RuntimeException {

Choose a reason for hiding this comment

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

폴라는 어떤 기준으로 커스텀 예외를 생성하나요?


private static final String MESSAGE = "해당하는 쿠폰을 찾을 수 없습니다.";

private final HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

Choose a reason for hiding this comment

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

위의 값들도 ErrorConstant로 관리할 수 있지만, 클래스 내 상수로 관리하기로 결정한 이유가 궁금해요!

import java.time.LocalDate;

@Getter
public class CouponDomain {

Choose a reason for hiding this comment

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

도메인과 엔티티를 분리했네요! 도메인과 엔티티를 분리한 이유를 공유해주세요:)

import coupon.common.ErrorConstant;
import coupon.common.exception.CouponException;

public class PriceInformation {

Choose a reason for hiding this comment

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

할인율에 관련된 비즈니스 로직을 객체로 관리하는군요!
저는 이 부분은 쿠폰에 대한 비즈니스 정책의 일부라고 생각하여 쿠폰 도메인 내에서 검증만 진행하고 있는데요, 폴라는 이 부분을 도메인으로 분리하신 이유가 궁금해요:)

}

private void validate(String name) {
if (name == null || name.isEmpty()) {

Choose a reason for hiding this comment

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

쿠폰 이름도 공백만으로 이루어진 이름을 허용하지 않으면 어떨까요? MemberName은 공백 이름을 허용하지 않아서 물어봅니다 ㅎㅎ

@Test
@DisplayName("이름의 길이가 기준보다 길경우 에러를 발생한다.")
void getName_WhenNameIsLong() {
assertThatThrownBy(() -> new CouponName("1111111111111111111111111111111111"))

Choose a reason for hiding this comment

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

Suggested change
assertThatThrownBy(() -> new CouponName("1111111111111111111111111111111111"))
assertThatThrownBy(() -> new CouponName("1".repeat(31)))


import static org.assertj.core.api.Assertions.assertThatThrownBy;

class IssuePeriodTest {

Choose a reason for hiding this comment

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

종료일자가 시작일자보다 앞서는 경우에 대한 테스트도 있으면 좋을 것 같아요:)

class PriceInformationTest {

@Test
@DisplayName("총 할인율이 가능한 범위가 아닌 1% 인 경우 에러를 발생한다.")

Choose a reason for hiding this comment

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

경계값은 아니네욥!
그렇다면 허용하는 범위 내에서 PriceInformation 생성에 성공하는 테스트가 있으면 어떨까요?

void memberName_WhenEmpty() {
assertThatThrownBy(() -> new MemberName(" "))
.isInstanceOf(CouponException.class)
.hasMessage(ErrorConstant.MEMBER_NAME_IS_NULL_OR_EMPTY.getMessage());

Choose a reason for hiding this comment

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

정~말 별거 아니긴 한데요 😅

Suggested change
.hasMessage(ErrorConstant.MEMBER_NAME_IS_NULL_OR_EMPTY.getMessage());
.hasMessage(ErrorConstant.MEMBER_NAME_IS_NULL_OR_BLANK.getMessage());

}

@Transactional(readOnly = true)
public Coupon searchCoupon(Long couponId) {

Choose a reason for hiding this comment

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

저도 폴라와 비슷한 생각을 했던 것 같아요! 그래서 저도 Write db를 조회하는 방법을 선택했는데요, 폴라와 차이점이라면 저는 관리자가 쿠폰을 조회하는 경우에 대해서는 Read DB를 거치지 않고, Write DB를 바로 읽는 방법을 선택했습니다!
저는 관리자는 일반 사용자보다 적으므로 TPS가 상대적으로 부하가 크지 않을 것이며, 관리자 입장에서 쿠폰을 생성한 직후 조회하고자 하는 니즈가 적을 것이라고 생각했어요.
쿠폰을 생성한 직후 조회하고자 하는 니즈가 적다는 것은 곧 쿠폰을 생성한 직후 조회하는 시나리오가 발생할 가능성이 적다는 것을 의미하기도 한다고 생각했어요! 그만큼 Write DB를 바로 사용하는 전략을 취하더라도, CUD 처리 성능 저하를 고려할 정도는 아니라고 생각했습니다.
제가 선택한 전략이 현재 미션에서 요구하는 특정 상황에서만 해결 방안이 되는 선택지일 수도 있겠지만, 인프라 지식이 얕다보니 더 멀리 고민해보진 못한 것 같아요🥲
그래서 더더욱 폴라의 의견이 궁금하네요!
폴라도 저와 비슷한 흐름으로 고민을 한 것 같은데요! Write DB로 바로 조회하는 방법을 선택하지 않은 이유는 무엇인지 궁금합니다:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants