향해99

qna

dev_0hoon 2024. 6. 19. 22:46

 

순차적으로 처리라는건 뭐임?

-> 동시에 요청이 들어오면 정합성을 지키면서 처리할 수 있고 (Lock)(Lock = 심화과제라고 생각하면 된다.)

   > Lock을 이용해서 충돌을 막고, 같은 유저ㅗ에 대한 요청이 있다며 ㄴ(입금/출금) 한번에 하나만 수행된다.

   > 이거 왜 넣었어요?

    1. 동시에 공유자원 ( 데이터) 에 여러 명이 접근 했을 때 발생하는 문제를 인지하고 있는지.

    2. 실제 APP 운영 시에 빈번하ㅔㄱ 발생하는 동시성 문제를 점검하기 위한 테스트를 작성할 수 있는지.

        ( 통합 테스트 )

-> reqeust를 우리가 Controller에서 받은 시점을 Timestamp로 찍어서 이걸 Queueing 한다던가 하는 방법으로 풀어낼 수 있다. (심화의 심화 = 완전 이거까지 고려한다면.. 1주차에서 더이상 내가 가르칠 게 없다 테스트 말고)

 

실제 뱅킹 어플리케이션이라면 순차처리는 이루어져야 합니다. ( 필수 )

대기열에서 예약 가능인원으로 빡빡하게 한명 씩 들여보내고 싶어. ( 필수 )

 

동시에 4명이 에르메스 백 산다고 요청함 (근데 이 때 재고는 2개임)

근데 4명 모두에게 주문완료 처리가 되면 안된다.

그러려면 동시에 재고에 접근하도록 하는 것을 방지 할 수 있어야 함.

----> Lock 이라는게 뭔지 찾아봐야겠다.

 

그래서 과제 어쩌라고?

-> 만약 유저 1에 대해 입금 1번 출금 2번이 요청됬다면, 유저의 잔액은 입금 1번 (+) 출근 2번 (-)이 모두 처리되어 계산된 금액이어야 한다. 과제 상 "Redis" 사용 금지 ( 코드 레벨에서 풀어라 )

DB가 아니니까 낙관락/비관락 이런거 사용하지 않는다. 어떻게 해야할까?

 

 

TDD를 하는 모습

 

A를 짜야한다. 요구사항 분석

1. 유저가 잔액이 부족하면 주문이 실패한다.

2. 상품이 썩었으면 주문이 실패한다.

3. 상품 재고가 주문이 실패한다.

TC 작성 (~~면 안된다)

 

@Test

void 유자가 잔액이 부족하면 주문이 실패한다() {

   user = 100원

  주문(300원 어치);

  예외 터지나?

}

 

이건 당연히 실패한다. 주문() 이라는 기능이 없어서.

그래서 이제 주문() 이라는 기능을 짠다.

그러면 테스트 코드를 작성하면서 우리는 주문이라는 기능에,

어떤 값들을 (함수 파라미터)를 우겨넣어야 하는지를 파악했다.

그걸 기반으로 조약하게 짭니다.

 

서비스와 이프문과 포문이 없어야 된다고 생각한다.

주문한다() {

  유저 = 유저조회()

 if( 유저.잔액 < 주문 금액) {throw Exception}

  상품 = 상품조회()

if (상품.상태 = 썩음) {throw Exception}

if (상품.재고 - 주문개수 < 0) {throw Exception}

 

...

return 주문정보

}

 

3. 테스트코드를 전부 성공시킴.

4. 내 코드를 본다. (개못생김)

5. 리팩토링을 한다. (기능을 분리했ㄷ마ㅕㄴ 테스트 코드 이관 작업도 같이함)

--- (1) 부터 다시 반복

 

한 두 싸이클 정도 돌리면 기능이 완성된다. = 이게 TDD 방식으로 개발하는 것.

--------------------TDD의 꿈과 이상편

개발자 : 마감기한이 있는 일꾼

테스트 코드에 익숙하지 않고, 테스트 케이스를 짜는게 미흡해 => 요거때매 딜레이 되서 기능이 제대로 안된다? => 못하는 개발자

 

<현실편>

1. 요구사항 분석한다.

2. 요구사항을 만족시키는 조악한 코드를 짠다.

3. 그 요구사항의 실패 CASE 들을 테스트코드로 짠다.

4. 다 통과 시킨다.

5. 개선한다 ( 리팩토링 ) -> 1~2부터 반복

 

 

4. 동시성 테스트

Ts= 아고라에 답 오림

 

 

CountDownLatch.await() | CompletableFuture.allOf(병렬로 수행할 함수 다 때려박음).join()

요걸 통해서 병렬로 스레드 열어서 작업시키고 다 기다리게 할 수 있다.

그러면 동시에 특정 함수를 여러명이 요청하는 것처럼 짤 수 있는데, 요걸로 데이터 정합성이 지켜지는지 테스트해봄.

** 주의할 점 **

이번 과제를 예로 들어보면

내가 위처럼 짯는데, 각 요청의 유저가 다다름 = 동시성 문제를 검출할 수 있냐 ?

어떻게 짜야된다? 같은 유저에 서로 다른 상호작용들을 동시에 실행시키도록 테스트를 해봐야한다.

 

 

Repository 계층 = 단위 테스트 안함 ( 단순 CRUD 기 때문에 본인만의 고유 로직이랄게 없음)

+ JPA / TypeORM, Prisma 이런거 쓰는데 ORM 쓰는 이유 무엇? '신뢰'다, 그니까 단순히 호출하는거 테스트 안한다.

 

Service <> Repository Interface <> RepositoryImple (Ioc = 의존의 역전)

Service 를 유닛테스트 한다 = RepositoryInterface 를 Stub / Mock 해서 서비스의 고유 로직만 테스트한다.

Service를 통합테스트 한다 = RepositoryImpl 물리고 실제로 데이터 흐름이 제대로 이루어지는 등을 검사한다.

 

단순 조회 = 사실 동시성이 지켜지지 않도도 된다. 요청 시점에서 반환하고 끝나기 때문에.

여러분들 뱅킹앱 생각해보면 >> 돈 보내거나 돈을 받아야할 때 Pull to Refresh로 오지게 당겨보죠?

그러다가 언젠가 새로고침 됨. (매번 조회하지만)

 

 

 

PR

Git : develop / main 브랜치

여기서 'git switch - c feature/XX' 명령어를 cmd에 치면 그 브랜치 기반으로 새로운 브랜치 따짐

그다음에 작업을 하면서 Commit을 한다.

Github에 'git push origin feature/XX' 커밋 다 올린다. (푸시로)

그러면 PR 칸에 가면 create pull request 가 있다. 이걸 누르면 어디서 어디로 PR 할건데? 가 뜨는데 'feature/XX' -> 'develop' / 'main' 요러헥 잡으면 Github 이 변경점들 (새로 추가된 커밋) 등만 쇽쇽 뽑아준다.

그러면 나는 PR 메세지를 행복하게 작성하고,

나 이런거 잘 짯나? 좀 의문이 든다. 아니면 칭찬받고 싶다 하는 포인트가 있으면,

PR 메세지에 '요런 거 중점적으로 봐주세요' 작성한다. => 코치들이 코드 리뷰 해줄거다.

 

유닛테스트에는 @SpringBootTest가 붙으면 안됨,

그건 통합테스트임

 

스프링/네스트든 이게 진자 xxService에 대한 단위테스트인가? 를 가장 쉽게 확인하는 방법.

 

@SpringBootTest / Nest의 Module이 주입없이 테스트를 해야한다.

- 즉, 우리의 서버 프레임워크의 도움이 없이 테스트를 할 수 있어야함.

 

Stub 한 Fake 객체 장점 = 상태를 내부적으로 관리할 수 있음.

뭐 예를 들면 DB Repository Interface를 Stub하면 내부적으로 Map<Long, User> 이런거처럼 DB 동작처럼도 만들 수 있음

근데 Mock은 '너 내가 호춣하면 이렇게 응답해' 니까 재사용은 안된다. 하지만 행동을 모방할 수 있기 떄문에 테스트를 위해서 나 이거해야하니까 너는 내가 원하는대로 움직여. 라고 명령 내리기 용이함

 

Stub ( 상태) vs Mock ( 행동 )

Stub의 장점 = UserRepository 라는 걸 Stub 한 UserFakeRepository 라는 구현체가 있다.

 그럼 UserRepository 쓰는 모든 단위 테스트에서 저거갖다 쓰면 됨.

Mock의 장점 = 테스트 마다 응답을 원하는 대로 지정하기 쉽다.

내가 A 함수를 테스트 하기위해서 A를 호출하는데, 내부적으로 UserRepository.getById를 호출한다? 그러면 Mock(UserRepository.getById).thenReturn(User(id=1, name =허재)) 너 내가 잠깐 너 속여놓고 호출되면 손인대로만 행동니까. 쉽다.

재사용이 안된다.

 

단점이라면 스킬풀하게 풍부하게 잘 사용하려면 MockLibrary의 도움을 받는거니까 라이브러리에 대한 러닝커브로 존재한다.

 

 

 

동시성처리

1) 이번 과제에서 "동시성 테스트"를 통해 발견한 문제를 해결하기 위한 방법 중하다.

2) 실무에서 저정도면 메모리 오버헤드 발생 안함.

- Lock 때문에 메모리 오버헤드가 발생하려면 1gib짜리 서버 기준

- Lock을 보관하는 ConcurrentHashMAp 때문이 아니라 잠겨있는게 오랫동안 해결이 안되서

- Lock을 기다리는 요청 스레드가 많아져서 메모리에 문제가 생김.

(걔네들은 계속 기다릴거고, 새 요청들어왔는데 또 기다릴거고 => 스레드 계속 늘어남 = 각 스레드가 쥐고 있는 메모리 계속 그대로 있ㅇ므. = 문제가 생긴다.)

 

10. 통합테스트만 병렬로 수행해도 충분하다.

E2E로 한다면 단순히 동시성 문제만 체크하는 건 통합테스트로 하고

진짜 요청이 몰렸을 때, 동시성 대기/교착 문제 떄문에 요청스레드 혹은 요청들이 다 기다리고 있을 때 문제가 발생하는가? 같은 걸 보기 위해서 동시성 문제를 할 수 있다.

 

동시성 테스트

- 대상에 따라 내가 확인해야 되는 것이 다르다.

 

 

 

Mock이 아닌 Stub처럼 가짜 객체를 만드는 방법도 있다.

 

controller

DTO <> Service가 원하는 input / outpuit으로.. 그게 단순 함수 파라미터 일수도 있고, 

---

Serivce

Repository Interface

Domain

<Domina은 중심이다. > 

---