[Spring] Spring Boot 프로젝트 고군분투 3주차 리뷰

우디혜 2020. 11. 28. 16:59

2주차... 이전에 3주차 코드 리뷰를 먼저 정리해보려고 한다..!

1. Converting String to Numeric value

처음에 나의 코드는 아래와 같이 NumberFormatException에 대한 고려를 전혀 하지 않은 코드였다.

만약 nativeWebRequest.getParameter("offset")이라는 코드에서 "0L" 숫자가 아닌 문자가 섞여있다면 바로 예외 상황이 발생할 것이다.

 Long offset = Long.parseLong(nativeWebRequest.getParameter("offset"));

✨ 이후 예외 상황으로부터 안전한 코드를 만들기 위해서 Apache Commons에 있는 NumberUtils를 활용했다.

// toLong(String str, long defaultValue) => Convert a String to a long, returning a default value if the conversion fails.
Long offset = NumberUtils.toLong(nativeWebRequest.getParameter("offset"), DEFAULT_OFFSET);

 

👍 그리고 여기서 좋았던 인사이트 중 하나!

모든 포스트를 조회하는 Action을 수행할 때, 페이징에 필요한 유저 입력값을 함께 가져온다. 그래서 이 Numerical Convertion을 수행하는 이유가 페이징에 필요한 유저 입력 값을 처리하기 위해서였는데, 사실 메인 비즈니스 로직은 모든 포스트를 가져오는 것이기 때문에 페이징 입력 값이 오류가 나더라도 정상적으로 실행이 되도록 해야한다(예외가 발생하면 default 값으로 대체하는 등). 예외 처리도 상황에 맞게 잘 해야한다..🤔 은근 생각해야할게 많당

 

 

2. SupportsParameter() in custom HandlerMethodArgumentResolver class

HandlerMethodArgumentResolver 인터페이스를 상속받아 PageableHandlerMethodArgumentResolver라는 클래스를 만들었다.

@Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType().equals(Pageable.class);
    }
@Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return Pageable.class.isAssignableFrom(methodParameter.getParameterType());
    }

코드 리뷰 요청 시에 HandlerMethodArgumentResolver를 쓰는 목적에 관한 질문을 했는데
감사히도 정말 자세하게 답변해주셨당(❤).. 그 밖에 다른 질문들도 정말 많았는데..ㅎㅎ 그것들까지도 감사합니당🙇‍♀️

📢 HandlerMethodArgumentResolver 사용 목적

  • 컨트롤러 파라미터로 사용자 정의 타입이 선언되었을 때(나의 경우에는 페이징 처리를 위한 Pageable 클래스를 만들었다),
    Request 파라미터에서 사용자 정의 타입 생성을 위해(resolveArgument에서 수행되는 내용을 말하는 것같다) 사용한다.
    이 과정에서 다양한 validation이 수행될 수 있고 Controller에서는 덕분에 안전하게 Pageable을 파라미터로 사용할 수 있다.
    → 한 마디로! 파라미터를 스프링이 사용자 정의 객체로 바인딩할 수 있도록 해주는 역할 + validation 처리

3.JOIN

Inner Join / Outer Join 설명 링크.

Join 대상이 되는 A와 B table이 있을 때,
Inner Join은 A와 B 두 테이블에서 모두 matching이 되는 값이 있을 때만 join을 허용한다
집합으로 따지면 A ⋂ B에 해당되는 부분만 가져올 수 있다.

 

Outer Join은 매칭이 되지 않더라도 row를 만들 수 있다. 매칭 되는 값이 없는 경우에는 null값으로 대체한다.

Outer Join에는 3 가지 종류가 있다. A가 왼쪽 그리고 B가 오른쪽에 있는 테이블이라고 가정하자

 

Left Outer Join

  • 왼쪽 테이블인 A를 기준으로 Outer Join 작업을 수행한다. 때문에 A에 해당하는 field의 값들은 모두 채워져 있지만, 현재 A의 row 정보와 matching되는 B가 없으면 해당 row의 B field는 null로 대체된다. A에 해당.

Right Outer Join

  • Left와 반대 상황. 오른쪽 테이블인 B를 기준으로 Outer Join 작업이 수행된다. B에 해당.

Full Outer Join

  • A 테이블에는 값이 존재하는 데 B 테이블에 없으면 B field는 null값으로 대체해서 가져오고 반대의 경우도 마찬가지. 'A ⋃ B'에 해당되는 부분

natural join과 inner join의 차이

4. map()

코드 리뷰에서 잭슨님이 map을 사용하여 기존 코드보다 fluent한 코드를 제시해주었다.

public Optional<Post> like(Long postId, Long userId) {
        checkNotNull(postId, "postId must be provided.");

        return postRepository.findById(Id.of(Post.class, postId), Id.of(User.class, userId))
                .map( post -> { ... });    
 }

확실히 map과 lambda function을 활용하면 가독성이 좋아지는 것같다. 아직 익숙하지가 않지만 계속 쓰다보면 눈에 익겠지😅


👉 map() 함수를 자세히 뜯어보면 T 타입(여기서는 Post)을 들고 U타입으로 바뀌는데 여기서 주목할 점은 리턴 값을 Optional로 감싸준다는 것!!

5. Exception Handling

로깅 레벨

로깅 레벨에는 크게 debug, info, warn, 그리고 error(때에 따라서는 fatal까지도) 4가지가 있다. 이번 과제들에서는 resource 디렉토리 아래에 logback.xml 과 같은 파일을 만들어서 로그들을 관리한다(레벨에 따른 활성화 여부 등).

 

debug 로그

  • 말 그대로 디버그 시에 유의미한 정보들을 제공할 때 사용한다. 보통 개발 혹은 테스트 단계에서만 활성화 시켜놓는다. debug 로그를 활성화시켜놓게되면 로그의 크기가 너무 커져서 서버 성능이 저하될 우려가 있기 때문이다.

info 로그

  • 현재 프로그램이 어떠한 설정들을 가지고 구동되고 있는지에 대한 정보를 제공할 때 사용한다. 현재 연결되어있는 포트 정보 등이 이에 해당된다.

warn 로그

  • 정상적인 상황은 아니지만 즉각 대응을 할 필요는 없는 상황을 나타낼 때 사용한다. 에러가 발생할 수도 있을 이상 징후들을 미리 감지해주는 역할.

error 로그

  • 즉각 대응이 필요한 비정상적인 상황을 알려줄 때 사용한다.

이렇게 예외 상황의 상황과 정도에 따라 로그 레벨이 분류되기 때문에 모든 예외에 대해 기계적으로 warn, error 로깅을 남기는 것은 적절하지 않다.

예외 처리의 중요성

평소 예외 처리에 대해서 그다지 신경을 많이 쓰는 편이 아니었지만, 예외 처리는 생각보다도 더더더 중요했다.
특히 warn과 error 로그는 한 번 warn과 error 로그가 발생하면 백엔드 담당자에게 실시간으로 알려주는 경우가 많기 때문에 주의해서 사용해야한다.(야근 당첨...?ㅎ 이 말씀을 듣고 확 와닿았다ㅎㅎ....😱) 로그를 남기는 가장 큰 목적은 예외 상황에 대한 구체적인 정보를 잘 파악하기 위함이다. 그렇기 때문에 예측되지 않은 예외 상황에 대한 로그를 남길 때는 stack trace 등 디버깅에 도움이 되는 정보들을 남기는 것이 중요하다. logback, log4j2 로깅 라이브러리들을 잘 활용하면 좋다.

 

∴ 어떤 예외 상황에 어떤 로그 레벨을 설정할 지, 어떤 로그 메시지를 남길 지 잘 판단해야한다.(그게 가장 어려운 것같다..😂)

 

라이브 세션에서 예를 들어준 것은,
Bad request에 대한 Exception (IllegalArgumentException 등)은 client 측에서 발생한 문제이기 때문에 서버 측에서는 아무런 작업도 해줄 필요가 없다. 그럴 경우에는 그냥 log.debug("Bad request exception occurred : {}", e.getMessage(), e) 와 같은 간단한 로그 메시지를 남기면 된다. 그러나 우리가 예측한 예외상황(custom exception handler 메소드로 처리가 불가능한)이 아닌 exception이 발생할 경우에는 warn 혹은 error 로그를 띄워서 예외 상황에 대해 확인해볼 필요가 있다.

6. 필요한 객체 식별

과제에서 포스트에 좋아요를 누르는 기능을 만들기 위해서 like entity를 만들어주어야할지 고민이 많았다. DB에는 like라는 table이 존재했지만 post pk와 user pk의 조합으로 이루어져 POST와 USER 테이블의 관계를 나타내기 위해 만들어졌기 때문이다(다시 말해, 실제 모델로서의 역할은 하지 않는 듯했다). 코드 리뷰에서는 entity를 식별하는 것에 정답은 없기 때문에 크게 상관없다고 말씀해주셨다..!



확실히 3주차는 잭슨님이 예고(?)하셨던 대로 기술적인 부분(물론 나는 이것도 어려웠지만)보다도 어떻게 잘 할지가 어려웠던 과제였다. 정답이 없는 것이 많았고 간과할 수 있는 디테일한 부분들이 사실은 큰 의미를 지니고 있는 요소들도 많았다.