DTO가 뭐길래

송민준·2023년 11월 15일
0

우테코 프리코스

목록 보기
8/10

MVC를 공부하면서 혹은 프레임워크를 공부하면서 한번 쯤 들어봤을 DTO이다.
MVC를 보면 알겠지만 모델 / 뷰 / 컨트롤러로 컴포넌트를 나누고 컨트롤러를 사이에 두고 모델과 뷰를 분리시켜서 유지보수에 용이하도록 만든 것을 볼 수 있다.
MVC를 공부하면서 의문이 들었던 것은 "어쨌든 간에 뷰에서 모델이 가진 결과를 출력하기 위해 모델 객체를 뷰에 가져와야하는데 그렇게 되면 서로의 존재를 인지하고 의존성이 생기는게 아닌가?" 라는 의문이 있었다.
물론 뷰에서 모델 객체의 비즈니스 기능을 사용 안하면 그만이다. 하지만 꼭 그러리라는 보장이 있겠는가?
여기서 DTO가 의문을 해소시킬 수 있다.

DTO?

Data Transfer Object 의 약자이다. MVC의 장점인 모델과 뷰를 분리시키기 위해 사용하는 "데이터 전송 객체"다.
DTO는 어떤 비즈니스 로직을 담고 있어선 안된다. 비즈니스 / 도메인 로직을 다른 계층(View)에 노출시키지 않으려는 의도이다.
다른 계층에서 사용될 데이터와 getter만 정의해야한다.

DTO의 장점들이 있는데

모델의 속성들을 감춘다.

만약 모델 객체를 그대로 뷰에 노출시키면 어떻게 될까.
다음은 유저의 정보와 유저의 정보를 가져오는 기능이다.

// model
public class User {
	private String name;
	private int level;
	private String password;

	public User(String name, int level, String password) {...}

	private String getName() return name;
	private int getLevel() return level;
	private String getPassword() return password;
}

public class OutputView {
	public void printUserInfo(User user) {
		print(user.getName);
		print(user.getLevel);
		print(user.getPassword);
	}
}

어떤 유저의 정보를 가져오는 기능은 "누구나" 사용 가능한 기능이다.
그리고 유저 모델 객체에서 패스워드는 누구나 보면 안되는 정보다.
만약 OutputView 처럼 User 모델 객체를 그대로 받으면 어떻게 될까. View는 모델 객체의 민감한 데이터(패스워드)까지 접근이 가능하다.

물론 당연하게 유저의 패스워드를 그대로 노출시키는 개발자는 없을 것이다.
그러나 패스워드가 아니라 다른 정보, 그러니까 민감한 정보지만 요구사항을 이해하지 않고 보았을 때 민감해 보이지 않을 수 있는 정보라면 그대로 출력시킬 가능성이 있다. (물론 이런 민감한 정보의 getter를 만든게 실수일 수 있다. 하지만 구조상 어쩔 수 없이 민감한 정보를 getter로 넘겨야만 하는 상황이라면 이런 문제가 발생할 수 있다.)

하지만 DTO를 넘겨준다면 그런 가능성을 없앨 수 있다.

public class UserInfoDTO {
	private String name;
	private int level;

	public UserInfoDTO(String name, int level) {...}

	public getName() return name;
	public getLevel() return level;
}

DTO 클래스다. 이 DTO 클래스는 어떤 비즈니스 로직을 담고 있지 않기 때문에이 클래스를 뷰에 넘겨준다면 뷰는 오직 민감하지 않은 정보들만 접근할 수 있다.

불변 객체

앞서 보여준 코드에서 DTO 클래스는 setter가 없기 때문에 불변 객체이다. DTO 클래스는 불변 객체로 만들면 좋다.
그 이유는 불변성을 보장함으로써 데이터 전달 과정에서 DTO 안의 데이터가 바뀌지 않았음을 보장할 수 있다.

뷰로부터 비즈니스 로직을 보호할 수 있다.

public class Button {
	private int touchCount;

	public int touch() {
		touchCount++;
		return touchCount;
	}
}

(예시가 좀 억지스럽다)
만약 이런 모델 객체가 있고 뷰에서 이 모델의 touchCount를 출력한답시고 touch()를 호출하면 내부 상태인 touchCount를 의도치 않게 변경시킬 수 있다. 결과적으로 버튼 모델의 우리 의도대로 동작하지 못하게 만드는 원인이 될 수 있다.
하지만 DTO를 사용하면 touch()와 같은 비즈니스 로직이 아닌 touchCount 같은 데이터 자체를 반환하므로 비즈니스 로직을 보호할 수 있다.

적용

public class BadgeDTO {  
    private final String badgeName;  
  
    public BadgeDTO(String badgeName) {  
        this.badgeName = badgeName;  
    }  
  
    public String getBadgeName() {  
        return badgeName;  
    }  
  
    public static BadgeDTO from(Badge badge) {  
        return new BadgeDTO(badge.toString());  
    }  
}

뷰에서 Badge 모델 객체의 배지 이름을 출력해야한다. 모델과 뷰의 의존성을 분리하기 위해 BadgeDTO를 생성했다.
BadgeDTO를 생성하기 위해 생성자를 이용하는 방법도 있지만 from메서드처럼 "정적 팩토리 메소드"를 이용하는 방법도 있다.

정적 팩토리 메소드

생성자가 아니라 메서드를 통해서 객체를 생성하는 메서드이다. (객체를 생성하는 역할을 가진 메서드)
정적 팩토리 메서드를 통해 DTO를 생성하는 방식의 장점은 DTO를 생성하는 클라이언트가 BadgeDTO와 Badge의 내부 상태, 구현에 대해서 몰라도 된다는 점이다. 클라이언트는 그저 BadgeDTO.from(badge)로 쉽게 DTO를 생성하기만 하면 된다.

Reference

profile
개발자

0개의 댓글