DTO의 사용 범위에 대하여

후니팍·2023년 3월 13일
2
post-thumbnail

DTO란?

DTO(Data Transfer Object)는 계층 간 데이터 전달을 목적으로 사용되는 객체입니다.
MVC 패턴에서 domain <-> controller <-> view 간 데이터를 주고받을 때 사용됩니다.


학습 배경

저는 우아한테크코스 시작부터 DTO를 이용하여 view에 데이터를 전달했습니다. 이번 블랙잭 미션에서 유난히 DTO를 많이 사용했는데, 이 DTO의 사용 범위에 관하여 리뷰어분과 의견을 주고 받았습니다. 의견을 주고 받으며 DTO의 사용 범위와 MVC에 위배되지 않는 선에서의 DTO에 대해 더 깊게 공부해보았습니다.


문제점

controller는 view와 domain을 연결시켜주는 역할을 수행합니다. 따라서 view에 도메인 데이터 전달을 위한 dto를 controller에서 생성해야한다고 생각했습니다. 하지만 제가 설계한 블랙잭 미션에는 controller 뿐만 아니라 game이라는 service 레이어가 있었습니다. 게임 실행 로직을 모아놓은 곳이지요.

게임은 위 그림과 같이 view에서 입력한 커멘드를 controller로 전달하고, 전달된 값을 보고 game의 로직을 실행시키는 방식입니다. game에서는 domain들을 캡슐화시키고 로직에 따라 domain에게 값 변경을 요청합니다.

다음 그림은 처음에 저의 dto 생성 방식입니다.

controller에서 dto를 생성하는데, game을 통해 domain 인스턴스를 controller로 들고와서 dto를 생성했습니다.

    private PlayerStatusWithPointDto makeDealerWithPointStatus() {
        Player dealer = blackJackGame.getDealer();
        return PlayerStatusWithPointDto.from(dealer);
    }

    private List<PlayerStatusWithPointDto> makeChallengersWithPointStatus() {
        List<Player> challengers = blackJackGame.getChallengers();
        return challengers.stream()
                .map(PlayerStatusWithPointDto::from)
                .collect(Collectors.toUnmodifiableList());
    }

    private void showRank() {
        Result result = blackJackGame.makeResult();
        ChallengerResultDto challengerResultDto = new ChallengerResultDto(result, blackJackGame.getChallengers());
        DealerResultDto dealerResultDto = new DealerResultDto(result, blackJackGame.getDealer());
        OutputView.printFinalRank(challengerResultDto, dealerResultDto);
    }

service의 getter 메소드로 domain을 들고 와서 dto를 만들어주는 것이 controller의 역할인지에 대해 언급을 받았습니다. 저는 controller에서 당연히 dto를 생성해야한다고 생각했고, 인자로 domain이 들어가는 것은 불가피하다고 생각했습니다. 인자로 service를 넣는 것은 더욱 어색할테니까요.

또, domain에서 DTO를 생성하게되면 마치 view를 아는 상태로 dto를 생성하는 것처럼 보이기 때문에 문제라고 생각했습니다. domain은 view를 몰라야하니까요.


해결 방안

리뷰어께 받은 DTO 관련 링크입니다. 해당 글을 읽어보면서 생각이 조금 바뀌었습니다. 레이어간 data 전달이 DTO의 목적이 맞습니다. 하지만 view <- controller를 위한 dto와 controller <- domain를 위한 dto의 필드가 다를 것이 없기 때문에 2중 이동을 해도 괜찮다고 생각하게 되었습니다.

또, dto를 생성할 때 인자로 domain의 인스턴스를 넣어주고 dto 내부에서 원하는 값을 필드로 설정하면 domain은 view를 모르는 것이 되지 않을까 생각했습니다.

위의 두 이유로 service에서 dto를 생성하여 controller로 넘겨주는 방법을 선택했습니다.
최종 확정된 코드는 아래와 같습니다.

	// controller
    private void showPoint() {
        AllPlayersStatusWithPointDto allPlayersStatusWithPointDto = blackJackGame.getFinalResult();
        OutputView.printEndStatus(allPlayersStatusWithPointDto);
    }

    private void showProfits() {
        ProfitDto profitDto = blackJackGame.calculateProfit();
        OutputView.printProfits(profitDto);
    }
	// game
    public AllPlayersStatusWithPointDto getFinalResult() {
        return AllPlayersStatusWithPointDto.of(players);
    }

    public ProfitDto calculateProfit() {
        Result result = Result.from(players);
        return ProfitDto.of(result, players);
    }

controller에서 굳이 service가 아닌 domain을 꺼낼 필요 없이 service 자체에서 dto를 생성하도록 했고, 덕분에 controller가 가벼워질 수 있었습니다.


마무리

이번에는 service에서 dto를 생성했지만, 여전히 service에서의 dto 생성에 대해 확신을 갖지는 못하고 있습니다. 다른 미션에서 다른 점들을 경험해보고 저만의 정답을 찾아가려 합니다.

profile
영차영차

2개의 댓글

comment-user-thumbnail
2023년 3월 13일

고민하는 모습 좋네요 ~

답글 달기
comment-user-thumbnail
2023년 3월 23일

dto는 정말 고민할수록 답을 모르겠어요 잘보고감니다

답글 달기