본문 바로가기

우아한테크코스/미션 정리

[코드리뷰 정리] Level 1. 체스 미션 -DTO

이번 체스 미션에서는 웹 적용을 하면서 새롭게 배운 개념들이 너무 많았다.
때문에 이번 미션을 진행하면서 새로 배운 개념들과, 휴가 주신 피드백을 정리하는 방안으로 기록해야겠다 ✍️

step1에서의 DTO

image

사실 DTO의 개념은 대략 알고 있으나,
전 미션까지는 DTO의 필요성을 잘 못느끼고 있어서 사용하지는 않았었다.
이때 같은 경우는 현재 기물들을 나타내는 Pices 객체들만이 존재할 뿐인데 View에서는 64개의 position에 대한 정보를 출력하기 위해
Pieces의 메소드를 통해pieces.findByPosition(CACHE.get(xy)); 로 도메인에 대한 의존성이 생기고 있었다.
그래서 이를 제거하기 위해 휴의 리뷰대로 DTO를 만들어 View에 필요한 데이터들을 전달해주기로 하고
이 방법에 대한 확신을 얻기 위해 휴에게 질문을 했었다.
하지만 질답을 하다가 이번 단계에서 DTO를 사용하는 것 보다 다음 단계 웹 UI를 적용하면서
DTO의 필요성을 뼈저리게 느끼기로 했다.
사실 지금 시점에서 돌이켜보면 piecesDto를 만들면 해결될 일인것 같지만, 말씀대로 꼭 DTO가 필요했던 시점이 아니였던 것 같다.
일단 휴와의 질답을 통해 얻은 DTO에 관한 이야기는 다음과 같다.

  • DTO는 데이터를 전송하기 위한 객체로서 로직이 없어야 한다.
    대신 model -> modelDto로 변환하는 과정에서 필요한 작업을 모두 수행하여 View에 필요한 데이터를 세팅할 수 있다.
  • DTO의 데이터를 사용할 때 로직을 수행하는게 아닌 생성 하는 시점에 미리 데이터를 다 세팅하고 DTO를 사용하는 시점엔 데이터로서만 사용한다.
    (이 부분이 dto 생성자에서 필요한 값 세팅을 마친다는 의미)
  • 값에 대한 표현을 어떻게 할지에 대한 ui 로직은 View에서 자체적으로 판단해도 된다.

또한 DTO를 적용하기 전 DTO에 관해 알아보고 약간의 정리를 했다.


📝 DTO vs VO 간단 정리

DTO VO 혼동의 이유

데이터 전달용 객체를 VO에서 TO로 변경되었기 때문에 혼동이 온다.

DTO = Data Transfer Object

"계층 간" 데이터를 전달하기 위한 객체로, 데이터를 담아서 전달하는 바구니 역할을 한다.

  • 용도 : 레이어 간 데이터 전달
  • 동등 결정 : 속성 값이 모두 같다고 해서 같은 객체는 아니다.
  • 가변 / 불변 : setter 존재 시 가변, setter 비 존재 시 불변
  • 로직 : getter / setter 외의 로직을 가지지 않는다.

getter/setter 메서드만을 갖는다.
보내는 쪽에서 setter를 사용해 값을 설정하고 getter를 사용해 값을 받는다.

setter 메소드를 가질 경우 데이터가 가변이기 때문에 필드를 final 로 하고 생성자를 통해 set을 하면 더 안정적이다.

➕ Entity

엔티티 클래스는 요청이나 응답 값을 전달하는 클래스가 아닌 데이터베이스와 매핑된 핵심 클래스다.
이 엔티티 클래스를 기준으로 테이블이 생성된다.
뷰와 엔티티가 직접 연결되어 있으면 엔티티에 변경이 일어날 때, 뷰의 모든 부분을 수정해주어야 한다.

VO = Value Object

값 그 자체를 표현하는 객체로 불변이어야 한다.

값으로만 비교되는 객체로 equals / hashCode 오버라이딩이 핵심이다.
생성자를 통해서만 값을 초기화해야 한다.

  • 용도 : 값 자체 표현
  • 속성 값이 모두 같으면 같은 객체
  • 가변 / 불변 : 불변
  • 로직 : getter / setter 외의 로직을 가질 수 있다.

step2에서의 DTO

View에 데이터를 전달하는 목적

4, 5단계를 진행하면서 웹 UI를 그리기 위해 Gson을 사용하였고, 이로 뷰에 보내줄 데이터를 Json으로 만들 수 있었다.
String chessGameListJson = gson.toJson(chessGameList);
이렇게 Gson 객체를 통해 string 형태의 json을 만들고 spark java에서 다음과 같이 res 객체의 body에 이 json을 담아 보낸다.

get("/chess-game-list", (req, res) -> {
  List<Integer> chessGameList = chessService.getAllChessGameId();
  return gson.toJson(chessGameList);
});

➕ 잠깐 Gson에 대해 좀 더 언급하면,
json의 키 값과 객체의 필드를 매칭 시키면 다음과 같이 req로 날아온 json 데이터를 객체로 쉽게 생성할 수 있다.

MoveRequestDto moveRequestDto = gson.fromJson(req.body(), MoveRequestDto.class);

Gson을 통해서 객체를 json으로 변경할 때에 gson이 객체의 필드에 있는 값들을 자동으로 key-value의 json으로 만들어 준다.
그런데, 객체 자체를 Gson을 통해 json으로 만들면
View에서 원하는 데이터 양식을 맞출 수 없고, 또 View에서 원하지 않는 데이터도 함께 들어가게 된다.

그래서 이때 필요한 것이 바로 DTO 였다.
View에 필요한 데이터를 원시값 필드로 가진 DTO를 만들고 View에게 데이터 전송을 위해 이를 사용하는 것이다.
이 경험 덕분에 휴가 step1에서 언급했던 DTO의 찐 필요성을 느낄 수 있었다.

DAO에서 잘못된 DTO 사용

DTO에 대한 개념을 살펴보면서 계층 간 데이터를 전달하기 위한 객체라는 사실에 집중하여
맨 처음에는 DAO와 Service 레이어가 주고 받는 데이터에서도 DTO를 사용하였었다.
이에 대해서는 다음과 같은 피드백을 받았다.

image

dao로 데이터를 전달하기 위해 서비스에서 dto로 싸고, dao에서 dto로 반환하여 서비스 레이어에서 꺼내 쓰는 이 과정이,
돌이켜보니 도메인에서 꺼내 쓸 수 있는 값들을 굳이 dto로 전달하면서 불필요한 dto 객체 생성과 코드만 늘어나고 있었다.
뷰에서는 gson을 사용해 json으로 만들기 위해 dto의 필요성을 절실히 느낄 수 있었지만,
dao에서 불필요한 dto들을 제거했더니 dto를 불필요하게 사용하고 있었구나를 느꼈다.

데이터 전달 목적

imageimage

휴의 말대로 나의 구조에서, ChessGame 객체와 chess_game 테이블 간의 불일치는 명확히 존재했다.
사실 DB 연동 경험이 있지만 이번 미션에서 DB를 연동하는데 왜이렇게 막막할까 많이 고민이 되었는데,
객체를 테이블로 1:1 맵핑을 하려하다 보니 생긴 문제였다.
(휴의 말대로 나중에는 ORM을 통해 해결이 되겠지..!)
이번 단계에서는 객체와 테이블 간의 불일 치 문제를 DTO로 해결할 것을 제시해주셨고,
ChessGame에 대해서는 DAO와 Service 간에 데이터 전달에서 DTO를 사용하여 저장할 데이터를 주고 받도록 하였다.

  • ChessGameDao 일부
public ChessGameStatusDto findChessGameStateById(int chessGameId) {
  String query = "SELECT turn, IF(isFinish, 'true', 'false') isFinish FROM chess_game WHERE id = ?";
  try (Connection connection = dbManager.getConnection();
       PreparedStatement pstmt = connection.prepareStatement(query)) {
    pstmt.setInt(1, chessGameId);
    ResultSet rs = pstmt.executeQuery();
    if (!rs.next()) return null;
    return new ChessGameStatusDto(rs.getString("turn"), rs.getBoolean("isFinish"));
  } catch (SQLException e) {
    e.printStackTrace();
  }
  return null;
}
  • ChessService 일부
public ChessGame findChessGameById(int chessGameId) {
  ChessGameStatusDto chessGameStatusDto = chessGameDao.findChessGameStateById(chessGameId);
  List<Piece> pieces = pieceDao.findAllByChessGameId(chessGameId);
  return ChessGameFactory.loadChessGameByInfo(pieces, chessGameStatusDto.getTurn(), chessGameStatusDto.isFinish());
}

이에 대한 휴의 피드백은 OK 였지만 DTO에 대해서 리뷰어마다 다른 관점을 가지고 있을 것이라 하였다.


DTO에 대한 결론

DTO에 대해서 휴가 주신 참고 자료를 읽고, 이번 미션에서 경험한 DTO를 간략히 정리하고 마무리한다.

혹시 DTO(VO) 작성하시나요?

객체를 자동으로 Json으로 변경할 경우 객체에 존재하는 모든 속성과 조합 관계에 있는 객체의 속성까지 Json으로 자동으로 변환된다.
이 경우 View에서 불필요한 속성까지 Json으로 변환된다.
이 문제를 해결하기 위해서 객체에서 View에서 필요한 속성만을 뽑아서 DTO로 만들어 계층 간 데이터를 전달한다.

하지만 도메인에서 특정 속성만을 뽑아 데이터를 전달하고 싶다면, 추후에 Lombok을 통해 필요한 속성만을 전달할 수 있다.