본문 바로가기
학습로그

[레벨2] 지하철 노선도 관리 미션 학습로그

by 에드박 2021. 5. 21.

[Architecture] Request, Response DTO - 3

학습 내용



  • 위의 피드백을 받고 고민을 시작
  • Request의 경우
  • 단순하게 Service (Application Layer)에서 Presentation Layer의 DTO를 받는것도 상위레이어를 참조하는 것이라 생각하여 이와같이 코드를 구현
  • DTO 를 왜 쓰는가에 대한 원론적인 물음으로 들어가면 -> 도메인을 보호하기 위함
  • 다시 코드를 보면 Controller -> Service 로 Request -> DTO 로 변환해서 보내주는것이 위의 목적에 부합하는가와 과연 장점이 있는가를 물어보면 대답은 NO
  • 다만 Service 가 비지니스 로직을 가지는 구조이고 Service 가 Service 를 참조하는 설계라면? -> DTO 를 만들어주는 것이 알맞음
  • 물론 위의 상황이 객체지향적인 상황은 아님 (내가 예전에 Spring 을 배우며 구현했던 만능의 Service가 떠오름)
  • 그렇다면 Response는?
  • Service는 비지니스 로직의 순서와 트랙잭션 관리 라는 흐름제어 역할 -> 과연 서비스의 메서드 반환값이 재사용될 일이 있는가?! -> Response를 반환해도 재사용 하는곳이 있는가? 라는 관점에서 생각하면 답이 나온다.
  • 재연링은 친절하다 👍

[Null] Collection.emptyList() - 1

학습 내용

  • 컬렉션 필드에 null로 초기화 하는것은 올바르지 못함
  • 해당 필드 또는 객체를 사용하는 코드에 null인지 체크하는 코드를 항상 추가해야함
  • (그렇지 않으면 NullPointerException이 발생할 가능성이 있으므로)
  • 빈 컬렉션(emptyList)로 초기화해주면 사용하는 쪽에서 null에 대한 두려움을 줄여줄 수 있다!

Collections.EMPTY_LIST vs Collections.emptyList()

  • Collections.emptyList() 가 타입 세이프하게 사용할 수 있습니다.
  • Collections 의 EMPTY_LIST를 사용하게 되면 Raw Type(원시 타입 즉, Object) 를 사용하게 됩니다.
@SuppressWarnings("rawtypes")
public static final List EMPTY_LIST = new EmptyList<>();
  • 하지만 Collections.emptyList() 메서드를 사용하면 내부 로직에서 EMPTY_LIST를 형변환하여 반환해줍니다.
@SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}

[Repository] Repository vs DAO - 2

학습 내용

  • 나는 Repository라고 쓰던것이 사실 DAO 였다 ㅎㅎ..
  • DAO 가 DB와 1:1 관계로 존재하고 Repository에서 집합처리를 한다고 생각하면 어떤 설계가 나와야 할지 보임
  • Line 안에서 Sections 라는 객체가 항상 필요해서 LineRepository 와 SectionRepository를 함께 LineService에서 사용했음
  • 현재는 LineRepository에서 LineDao와 SectionDao 를 주입받아서 사용하도록 수정
  • Repository는 객체의 컬렉션으로 Repository의 interface는 도메인 계층에 존재합니다. 그리고 Repository interface의 구현체는 Persistance 계층에 존재합니다. 즉, Repository는 추상화를 통해 Persistance 계층에 있는것을 숨기고 이로 인해 Repository에 도메인 로직을 사용할 수 있습니다.
  • 주의할 점은 만약 우리가 만든 도메인이 빈약한 도메인이라면 Repository는 그저 DAO의 메서드를 호출하는 정도로 끝날것입니다.

[OOP] Validator - 1

학습 내용

  • Sections 의 코드가 150줄이 넘어가서 추가 삭제등을 검증하는 로직을 Validator 라는 이름의 클래스로 분리
  • 분리 후 고민
  • 다른 개발자가 Sections 를 봤을때 검증 로직을 쉽게 찾을 수 있을까?
  • 항상 Sections 와 Validator를 번갈아 봐야하는 상황이 오지않을까?
  • 도메인 객체 자기 자신이 자신을 검증하는것이 올바르다.
  • Validator 자체는 객체지향적이지 못하다

참고 자료

미션 피드백


[예외처리]  DB에 중복키 저장으로 인한 예외 처리 - 1

학습 내용

  • 학습 내용DB에 데이터 저장시 고유값 중복이 발생하면 어떻게 예외 처리를 해야할까?
  • 1. DAO에서 DuplicateKeyException 예외를 try-catch로 처리
  • 2. Service에서 DuplicateKeyException 예외를 try-catch로 처리
  • 3. Service에서 저장 로직 실행 전 exist 조회 쿼리를 날려서 중복값이 있는지 확인
  • 만약 3번 조회 쿼리를 날려서 중복값 확인을 사용하더라도 저장 요청이 동시에 온다면?
    • 이름에 unique 제약사항 있음
    • 1번 2번 요청이 동시에 왔는데 둘다 이름이 "찰리" 인 데이터를 저장 요청
    • 1번 2번 둘다 조회 쿼리를 날려서 중복 검사를 통과
    • 하지만 저장 과정에서 DuplicateKeyException 발생!!
  • 위의 과정을 보면 중복 확인 쿼리를 날린다해도 DuplicateKeyException에 대한 핸들링은 필요
  • 한다면 중복값 확인 & DuplicateKeyException 둘 다 하는 방향으로 하기로 결정
  • 1번 방법에서의 DAO에 try-catch 로 결과 처리 로직이 생겨버린다면 DAO는 테이블과 1:1로 매칭 되는데 결과에 대한 처리를 고정시켜버리면 결과처리에 대한 자율성을 떨어트리는 효과가 발생 -> DAO에서 결과 처리 로직을 사용하는 것은 지양하자

참고 자료

미션 피드백


[예외처리] 예외처리 전략 - 4

학습 내용

@ExceptionHandler(SubwayException.class)
    public ResponseEntity<ExceptionMessageDto> duplicatedException(final SubwayException subwayException) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new ExceptionMessageDto(subwayException.getMessage()));
    }
}

 

  • 위의 코드는 SubwayException(커스텀 예외 중 최상단 예외)를 핸들링 하는 코드입니다.
  • status 가 항상 BAD_REQEUST 임을 확인할 수 있습니다.
  • 앞으로 SubwayException에서 발생하는 예외가 모두 400 에러 코드인 Bad Reqeust 인가?
  • 다양한 예외를 적절한 status로 던져주지만 예외마다 ExceptionHandler 메서드를 1대1로 만든다면 너무 비효율적
  • 아래와 같은 코드로 유연하게 예외 처리를 할 수 있도록 구현
public class SubwayException extends RuntimeException {
    private HttpStatus httpStatus;
    private ExceptionResponse body;

    public SubwayException(String message, HttpStatus httpStatus) {
        super(message);
        this.httpStatus = httpStatus;
        this.body = new ExceptionResponse(message, httpStatus.value());
    }

    public SubwayException(String message, Throwable cause, HttpStatus httpStatus) {
        super(message, cause);
        this.httpStatus = httpStatus;
    }

    public HttpStatus httpStatus() {
        return httpStatus;
    }

    public ExceptionResponse body() {
        return body;
    }
}

[테스트] 인수테스트 - 2

학습 내용

  • 인수테스트란?
    • 요구사항에 맞춰 비지니스 관점에서의 테스트, 사용자의 관점에서 이뤄지는 테스트라고 할 수 있습니다.
    • 비지니스 관점에서는 데이터베이스에 어떤 방식으로 저장, 수정, 삭제가 이뤄지는지는 중요하지 않다.
      • 따라서 인수테스트에서는 DB를 사용해서 데이터의 실제 적용 여부는 검증하지 않아도 괜찮다.
      • DB를 검증하는 것은 인수테스트의 비즈니스 로직을 테스트한다는 목적에 벗어난 행위
      • 비지니스 관점에서는 API의 사이클을 테스트
    • 여담
    • TDD 에서도 단위테스트를 할 때 최대한 테스트하기 쉬운 구조로 만들고, 의존성을 제거하는 것을 중요시
    • ATDD도 마찬가지 다른 의존성을 제거하는 것을 중요시
    • End-To-End(전 구간 테스트)와 다른 점은 테스트의 관점이 다릅니다.
      • 전 구간 테스트는 시스템의 관점에서 전체 구간을 테스트하는 것이며
      • 인수테스트는 비즈니스 관점에서의 테스트를 진행합니다.

참고 자료

미션 피드백


 

댓글