BackEnd Developer, Love Crossfit, Welcome to Jay's blog

프로그래머스 웹백엔드 스터디 7기 / 3주차 - 비즈니스 로직 1 좋아요와 페이징처리

|

프로그래머스 웹백엔드 스터디 7기 활동을 하면서 공부한 내용과 기능 구현시 했던 고민들을 정리한 내용입니다.


Index

  • 스터디 3주차
    • 느낀점
    • 고민
    • 공부한 내용
    • TODO 기능 구현
  • References

스터디 3주차

비즈니스 로직 구현 1 - 좋아요와 페이징처리

느낀점

2주차에서는 Spring Securiy 기술 자체에 대한 이해와 공부가 필요했다면 이번주는 좀더 비즈니스 로직에 초점을 맞춰서 예외처리와 로깅에 대한 고민 그리고 클린 코드의 관점에서 생각 해볼 수 있는 주차였다. 기술적으로 어려운 미션들은 아니었지만, 예외가 발생했을 때 어떻게 처리할 건지, 검증의 대상은 무엇인지, 로그는 어떻게 어떤 내용으로 남길 건지에 대한 고민을 미션 내내 하게 되었다. 어떻게 생각해보면 이번 미션도 쉬운 건 아닌듯하다. 검증과 로깅에 대한 명확한 기준이 없어서 그런듯 하다. 스터디를 통해 계속 연습하다 보면 되겠지.

고민

  • 예외처리를 어디서 어떻게 해줘야 하나

    • Controller, Service, Model이 있는데 layer마다 예외처리를 모두 해줘야 하나? 그렇다면 layer 별로 어떤 값을 어디서 검증을 해줘야지? 매우 고민스러웠다.
    • layer 별로 예외처리를 해줘야 하며, 특히 사용자 입력값은 어떻게 들어올지 모르니 반드시 해줘야한다.
    • Controller에서는 데이터 binding시에, Sevice에서는 비즈니스로직과 입력값 검증, Model은 생성자와 db로 업데이트될 필드
    • 어노테이션 말고도 Guava, common-lang3 등 validation check를 도와주는 라이브러리를 통해 가독성 좋게 예외처리를 할 수 있었다.
  • Optional의 용도. 언제 사용해야 하는 건지 감이 잘 안 잡혔다.

    • 유저를 조회하는데 메소드의 리턴값으로 Optional을 반환해줘야하는지 엔티티를 반환해줘야하는지 헷갈렸다. 각각 어떤 상황에서 사용해야 하는 걸까?
    • Optional에 대해 API 공식 문서를 찾아보았다.

      Optional is primarily intended for use as a method return type where there is a clear need to represent “no result” and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.

    • Optional의 주된 목적은 Null이 리턴될 수 있는 상황에서 Null값으로 인한 에러가 발생하지 않도록 이를 방지하고자 만든 메소드 반환 타입이었다.
    • 결국 NPE(NullPointException)에 대한 실수 방지 목적으로 나온 기술.
    • 그래서 전체 유저를 조회하는 할 때는, 아직 가입한 유저가 한 명도 없다면 빈값이 나와도 되는 null이 허용되는 상황이므로 List를 반환 타입으로 사용했다. id나 email로 유저를 조회할 때는, Null이 나오면 NullPointerException 에러가 날 수 있는 상황이므로 Optional로 반환해주었다!
  • 예외 발생시 로그를 어떻게 남겨야 하는가

    • 어떤 기준으로 로깅 레벨을 정해야하는지, 에러 로깅에 어떤 정보들을 포함시켜야 되는지 고민되었다.
    • 나는 대부분의 예외에 대해서 warn log를 남겼는데 코드 리뷰 후, 그러면 안된다는 것을 알았다. (상세 내용은 밑에서)


공부한 내용

  • Swagger

    • API 문서를 자동화 해주는 툴
    • API 문서를 위키 같은 곳에 작성할 경우
      • 변경될 때마다 일일히 수정 해줘야 했고
      • 수정을 놓치면 API 스펙 문서와 실제 API가 다를 경우가 생길 수 있음
      • 클라이언트/프론트 개발자에게 변경 내용 노티 필요
    • Swagger를 이용하면 API 서버 빌드시 자동으로 문서가 생성되어 변경 후 자동 반영됨 -> 매우 편리
    • 대화형 API 콘솔 자동 생성
      • 콘솔 URL: http://localhost:8080/swagger-ui.html
    • Swagger, Spring Rest Doc
    • 단점
      • 최신의 것만 유지되기 때문에 히스토리 관리가 안됨
      • 문서화를 위해 어노테이션을 컨트롤러와 DTO에 달아줘야함 (어노테이션 도배)
    • @Api, @ApiOperation, @ApiParam, @ApiImplicitParam, @ApiModelProperty
  • 비즈니스 로직 개발시 고민해야 할 요소들

    • OOP 원칙에 따라 모델링
    • 객체 모델링 방법
      • Immutable 상태로 만든다 -> final 추가
        • 변경이 불가하므로 기존 객체를 변경하는 것이 아니라 빌더를 통해 새로운 객체를 만들어 사용해야 한다.
      • 객체의 필요한 행동(ex. User 모델의 최근 로그인 시간 필드)이 무엇인지 고민하고 점진적으로 Immutable 필드를 줄여나간다.
      • equals, hashcode, toString 구현을 꼼꼼히 한다 ??
      • 불필요한 생성자(기본 생성자, 안티패턴)는 노출시키지 않는다. ??
      • 생성 인자 갯수가 많다면 빌더 패턴을 고려한다.
      • 생성자 체이닝: 인자가 적은 생성자 내부에서 인자가 많은 생성자를 호출하는 방식으로 생성자 오버로딩 이용하기
    • 가능한 모든 사용자 입력은 유효성(validation) 검사를 수행한다
    • 가능한 Service 클래스의 모든 메소드를 테스트
  • 입력값 검증은 어디서 수행해야 좋을까?

    • Validation Check를 도와주는 많은 라이브러리들이 있다. 이를 적극 활용하자. 가독성도 좋다
      • Guava Precondition, Apache Common-lang3, JSR-303, 등등
    • 원칙적으로 Controller, Service, Repository 레이어별로 입력값 검증을 수행해야 한다.
      • 하지만, 비슷한 검증 로직이 반복되므로 Controller보다는 Service 레이어와 모델 생성자에서 입력값을 검증하는게 좋다.
      • 중복된다고 한 곳에만 넣어줄 경우, 다른 곳에서 호출시 예외처리가 안될 수 있다.
    • Validation 어디까지 해봤니?
  • Optional, Stream API, Lambda를 적극적으로 활용하자
    • Java8 이후에 나온 기능들
    • Optional
      • 함수의 리턴 타입이 null일 수 있을 때 사용
      • NPE(NullPointerException) 방지하기 위한 기술
      • NPE 방어를 위한 NULL 체크 로직이 필요 없어짐으로 코드 가독성과 유지 보수성 높임
      • of(): Null이 아닌값이 들어옴이 보장될 때 사용
      • ofNullable(): Null 값이 들어올 수 있을 때 사용
    • Stream과 Lambda
      • Java 8에서 함수형 프로그래밍 부분적 지원 위해 나옴
      • Stream API는 Java Collection(List, Array 등) 다양한 연산 지원
      • JAVA는 일급 객체를 지원하지 않기 때문에 Lambda 사용이 제한적
      • 함수형 인터페이스 (메소드가 하나인 인터페이스 ex. Runnable)는 람다로 표현 가능
      • Stream API와 Lambda 사용시 코드가 훨씬 간결해지고 가독성이 향상됨
  • ControllerAdvice
    • Controller를 대상으로 예외처리를 공통적으로 할 수 있게 도와주는 AOP 어노테이션
    • AOP??
  • Logging에 대하여
    • 로깅의 목적
      • 문제가 발생했을 때 디버깅을 쉽게 하기 위함이다. 즉, 문제의 원인을 쉽게 추적하기 위함
    • 따라서 디버깅에 도움되는 유의미한 정보를 최대한 많이 남겨야 한다.
    • 그렇다고 로깅을 남발하면 안된다. 어떤 로그 레벨로 남겨야 할지 고민이 필요하며 상대적으로 중요하지 않은 정보를 warn이나 error 레벨로 남기면 안된다.
      • 보통 에러가 나면 실시간으로 개발자에게 가도록 알림 시스템이 구축되어 있다. 모두 에러로그로 도배하면 알림 지옥. 근데 별거 아닌 내용. 이 과정 반복되면 에러에 대한 불감증 생겨 큰 장애 발생시 대응이 늦어질 수 있음.
    • 로깅 레벨
      • debug: 개발단계나 테스트 서버에서 활성화. 테스트 중 이상 현상 발견시 최대한 많이 남겨서 추적. 프로세스 처리 순서 / 흐름 분석에 도움되는 정보 등
      • info: 디버깅 정보 외 어플리케이션 구동 관련 정보들 ex) 설정 정보들, 톰켓 뜨는데 바인딩 포트가 몇번이다
      • warn: 정상적인 상황은 아니지만 즉각 대응은 안해도 될때. 하지만 추후 확인은 필요한 정보.
      • error: 오류 발생. 즉각 대응 필요
      • fatal: error보다 높은 레벨
    • 운영환경에서는 보통 Info 레벨 부터. debug는 OFF. 안그러면 로그가 너무 많이 남게 되어 요청량이 많을 경우(수백기가) 서버 성능에 영향을 주게 된다.
    • debug는 개발, 테스트 서버에서 사용
    • 개발자가 예상치 못한 에러들이 발생했을 때 error 로그로 남겨놔야 한다.


TODO 기능 구현

1. 친구 포스트에 대해 좋아요 API 구현

  • PostService.like() 메서드 완성
  • 포스트의 likes 필드값이 1증가 해야함
  • 최초 좋아요 한번만 카운트 되며, 동일한 사용자가 중복으로 좋아요할 수 없음.
  • 좋아요 이후 포스트 조회 시포스트의 likesOfMe 필드값이 true이어야 함
  • 스키마는 likes 테이블 참고한다.

2. 포스트 목록 조회시 Paging 처리

  • offset, limit을 QueryParameter로 추가 (파라미터 생략시 기본값은 offset=0, limit=5로 처리)
  • 0~4까지 5개의 포스트가 있을 때
  • offset=0,limit=2는0~1까지 포스트목록을 조회
  • offset=2,limit=3는2~4까지 포스트목록을 조회
  • HandlerMethodArgumentResolver를 이용해 Pageable구현객체를 Controller에전달
  • likesOfMe값을 효율적으로 구하기 위해 단건 포스트 조회 및 포스트 리스트 조회SQL튜닝 필요

3. ControllerAdvice를 활용해 글로벌 에외처리

  • com.github.prgrms.social.controller.GeneralExceptionHandler를 작성
  • com.github.prgrms.social.error.ServiceRuntimeException 하위 예외클래스를 적당한 HTTP 오류로 매핑

4. 모든 REST API 및 관련 모델에 대해 Swagger 문서화


References

초보 웹 개발자를 위한 스프링 5 프로그래밍 입문
HandlerMethodArgumentResolver - https://jhkang-tech.tistory.com/49
ControllerAdvice - https://springboot.tistory.com/33