[스터디] Controller, Service, Repository 구분하기(4회차)

이호석·2023년 3월 16일
1

[스터디] Spring Boot

목록 보기
3/7
post-thumbnail

😁 배운 내용

간단한 API이라고 생각했지만, 심화 내용들을 추가하면서 꽤 많이 고민했던 미션들이었습니다.

처음 사용해본 RestTemplate

KRW -> USD를 변환하기 위해서는 현재 환율에 대한 정보가 필요했습니다.
따라서 외부 API를 호출해야 하는 상황이 있었고, Spring Framework는 해당 기능으로 RestTemplate을 제공해줍니다.

exchange라는 도메인으로 분리해 별도의 외부 API를 호출하여 사용하면서 RestTemplate의 사용경험을 할 수 있었습니다.

RestTemplate과 Connection Pool

RestTemplate을 사용하기 위해 정보를 찾아보던 중 RestTemplate의 모든 요청마다 새로운 Connection을 생성합니다.

JDBC에서 가장 많은 리소스를 잡아먹는 부분은 Connection을 할당받을때입니다. JDBC는 기본적 Coonnection Pool을 관리하기 위한 DataSource로 Commons DBCP2를 사용하고 있으며 여기서는 기본적으로 최대 8개의 Connection을 Pool에 할당하고 사용합니다.(Spring은 HikariCP를 사용)

Connection Pool 뜯어보기

하지만 RestTemplate은 모든 요청마다 Connection을 새로 생성하니 매번 많은 비용이 잡아먹히게 됩니다.

현재 메인 미션이 아니므로 Connection Pool에 대한 설정을 따로 해주지 않았지만, 후에 RestTemplate으로 외부 API를 호출해야 하는 일이 생긴다면 이 부분을 숙지해야겠습니다.
(참고로 Spring 5.x 버전부터는 WebClient를 사용하기를 권장하고 있습니다. 하지만, 이는 별도의 의존성을 추가해주어야 합니다.)

❗️ 참고
RestTemplate과 WebClient
RestTemplate에 관한 고찰



✏️ 미션 제출

언급하지 않은 코드에 대한 부분은 다음 Pull Request에서 확인할 수 있습니다. PR 링크

✅ 요구사항을 만족하는 api 만들기

1. 상품 등록 api

  • 요구사항
    - 클라이언트는 상품의 이름, 가격을 전송하면 상품을 저장한다.
    • 이미 동일한 이름의 상품이 존재하면 저장할 수 없다(Unique)

  • ProductController

    상품 이름, 가격을 가지고 있는 NewProductRequest를 통해 클라이언트의 데이터를 받아오고 service에게 상품 등록을 위임합니다.
    정상적으로 상품 저장이 완료됐다면 id가 생성되므로 dto로 감싸서 반환합니다.

  • ProductService

    dto객체를 domain으로 변환한다. 만약 동일한 이름의 상품이 존재하면 예외가 발생되고,
    그렇지 않다면 memory repository에 저장합니다.

  • ProductRepository

    저장될때 id값이 추가되며, List<Product>타입 store에 저장합니다.
    동시적인 사용자의 요청에서 동일한 id값이 생성되는 것을 막기 위해 counter 변수는 AtomicLong 타입입니다.

  • API 응답: POST /api/products

    • 상품 등록 성공(200 Ok)
    • 상품 등록 실패(400 Bad Request)

2. 상품 상세 조회 api

  • 요구사항
    • 상품 이름으로 상세 조회가 가능하다.
    • 가격을 원화와 달러를 구분하여 조회가 가능하다.
    • 없는 상품명으로 조회하면 실패한다.

  • ProductController

    조회 옵션을 쿼리 파라미터를 통해 받는다. 상품이름과 변환할 화폐단위는 상품 전체조회에서 선택 옵션이므로 Nullable 합니다.

  • ProductService

    name 옵션을 지정하지 않으면 모든 상품에 대해 화폐 단위만 지정합니다.
    name 옵션이 지정되어 있다면 해당 이름에 대한 모든 상품 + 화폐 단위를 지정합니다.
    (name은 unique값이므로 List타입이 반환될 수 없는 논리적인 오류가 있지만 pass!)

    여기서 ExchangeRatesService를 이용하는데 이는 외부 API를 통해 KRW 단위를 지정한 단위로 환율 전환을 했을때 해당 화폐로 변환하는 서비스를 제공합니다. 화폐 변환을 하는 작업을 ProductService에 포함시켜도 되지만, 상품에 대한 책임은 아니라고 생각해 별도의 서비스로 분리하였습니다.

    if (products.isEmpty())와 같이 name 옵션을 통한 조회에서 리스트가 비어있다면 예외가 발생됩니다.
    반대로 단순 respository.findAll()과 같은 경우에 예외를 발생시키지 않는데 보통 전체 상품 조회시 비어있다면 빈값을 그대로 보여주면 되기 때문에 발생시키지 않았습니다.

  • Repository

    조회되는 컬렉션안의 요소들은 수정할 수 없도록 unmodifiableList를 반환합니다.

  • API 응답

    • GET /api/products
      (200 Ok) 모든 상품 조회

    • GET /api/products?monetaryUnit=USD&name=컴퓨터
      (200 Ok) 특정 이름, Dollar 가격 상품 조회 성공

    • GET /api/products?monetaryUnit=USD
      (200 Ok) USD 달러 가격 표시로 모든 상품 조회

    • GET /api/products?monetaryUnit=USD&name=존재하지않는이름
      (404 Not Found) 존재하지 않는 상품 이름으로 상품 조회


✅ 조회/등록 실패시 응답 interface 구현

1. 조회/등록에 대한 일괄적인 처리

  • 요구사항
    • 조회, 등록 실패에 대한 공통적은 응답 방식이 필요하다.

  • 공통적인 처리를 위한 CustomException 구성

    공통적인 응답 방식을 구성하기 위해서 CustomException을 구성하였습니다. 현재 Service 레이어의 비즈니스 로직에서 발생되는 예외에 대한 처리가 필요하므로 BusinessException을 두고 이는 RuntimeException을 상속받습니다.
    모든 예외는 Unchecked Exception로 구성하여 예외 처리에 대한 부분을 다른 레이어까지 전이되지 않도록 할 수 있습니다.
    구체적인 Unchecked Exception을 던져야 하는 이유

  • ControllerAdvice

    ControllerAdvice를 통해 발생된 예외에 대해 공통적인 처리를 합니다.
    ErrorResponse는 예외에 대한 message를 Response Body에 담아 전송하는 커스텀 객체입니다.



🤔 개선 및 고민되는 부분들

테스트 코드

테스트 코드를 매번 작성할 때마다 항상 테스트 코드를 만들기 어려운 코드를 만나게 됩니다.
이런 코드들을 테스트하기 쉬운 유연한 코드로 변경하는 부분에 있어서 어려움이 큰 것 같습니다.

또한 전체 테스트들이 유기적이지 못한 느낌이 강합니다. 단지 단위 테스트로만 모든 커버를 하는 느낌이 큽니다. 좋은 코드나 테스트나 모두 잘 읽히는 것이 좋다는 생각인데 제가 작성한 테스트는 그런 부분이 약한 느낌입니다.

BDD 방식을 공부해서 테스트를 조금 더 개선해야 함을 느꼈습니다.

어설픈 완벽주의(?)

코드를 작성하는데 많은 시간이 들어갑니다.
열심히 하려고 하니까 좋은 거 아니야? 라고 생각할 수 있지만, 처음부터 너무 완벽하게 작성하고 싶은 욕심이 조금 큰 것 같습니다.

제일 이상적인 방식은 70% 정도의 퀄리티를 가지면서 빠르게 동작하도록 완성하고 이후 리팩토링을 하는 것이라 생각하는데 모그님은 어떻게 생각하시는지도 궁금합니다!

좋은 글을 작성하는 방법

구현한 코드에 대해 글을 작성할 때 깊이감과 어떤 방식으로 설명하는 것이 효과적일지에 대한 고민이 있습니다.
저는 4회차 미션의 주제인 계층의 분리를 생각하여 핵심적인 코드의 부분만 설명하고자 했습니다.
모그님이 보시기에 조금 더 보충해야 하는 부분이나, 추가했으면 하는 내용들이 있으면 알려주셨으면 좋겠습니다!

profile
꾸준함이 주는 변화를 믿습니다.

2개의 댓글

comment-user-thumbnail
2023년 3월 20일
  1. 테스트코드
    테스트코드에 대한 피드백을 드렸는데 고민하고 계셨군요! 테스트 코드를 다른 사람이 보기에 읽히기 쉬운 형태로 작성하는 것이 더 좋습니다. "~성공 테스트" 가 아니라 "~하면" "~와 같이 동작한다" 처럼 읽었을 때 각 상황별 응답을 이해하기 쉽도록요! given-when-then 패턴, BDD에 대해서 좀 더 조사해서 고민해보시면 좋을 것 같습니다~
  2. 완벽주의
    저는 그게 호석님의 강점이라고 생각합니다. 남들은 그냥 넘어갈 부분까지 고민해서 코드 퀄리티를 높이시니까요. 실제 업무를 할 때는 마감시간이 있기 때문에 고민만 많이 하는 것 보다는 일단 해보고 수정을 거듭하는게 더 효율적이긴 합니다. 그러나 혼자 공부하는 과정에서는 고민을 많이 할수록 좋지 않을까 싶네요~
  3. 좋은글을 작성하는 방법
    호석님의 글에서 개선할 점은 딱히 못 찾았는데 발견하면 말씀드릴게요ㅎ
    RestTemplate나, 예외처리 등등 미션하면서 해보신 내용들 중에서 하나를 타겟팅해서 깊은 글을 써봐도 좋을 것 같네요~

참고로 ResTemplate은 잘 안쓰는 추세입니다.(deprecated 예정...^_ㅠ) 대신해서 WebClient를 사용하는데요, 관련해서 찾아보시면 좋을 것 같아요~
https://happycloud-lee.tistory.com/220

1개의 답글