본문 바로가기
개발관련 이것저것

VO & DTO

by 이상한나라의개발자 2024. 5. 23.

VO(Value Object) : 값 객체 , 불변 객체

VO는 불변해야 하며, 이는 동일하게 생성된 두 VO는 영원히 동일한 상태임을 유지되어야 한다는 것을 의미합니다. 또한 VO는 잘못된 상태로는 만들어 질 수 없습니다. 따라서 인스턴스 된 VO는 항상 유효하므로 버그를 줄이는데 유용합니다.

 

 

특징

  • 불변성 : 모든 필드는 final로 선언되어 변경될 수 없습니다. 생성자로 초기화 되며, 이후 값이 변경되지 않습니다.
  • Equals & HashCode : VO는 값이 같다면 동일한 객체로 간주되므로 equals() 와 hascode() 메서드를 잘 구현해야 합니다.
  • 간단한 구조 : 주로 필드와 그 필드의 값을 반환하는 메서드들로 구성됩니다.

 

DTO ( Data Transfer Object)

메소드, 클래스, 프로세스 간에 데이터를 주고 받을 때 쓰는 모든 객체를 DTO라고 합니다. 

DTO 클래스를 만들어서 사용하는 이유는 다른 객체의 메서드를 호출하거나 시스템을 호출할 때 매개변수를 일일이 나열하는 것이 불편하기 때문입니다. 즉, DTO는 다른 객체나 시스템에 데이터를 구조적으로 만들어 전달하기 위한 객체입니다. DTO는 오롯이 데이터를 효과적으로 전달하는데 집중합니다. 그러므로 DTO에는 데이터를 읽고 쓰는 것외에 다른 비즈니스 로직이 들어가서는 안됩니다.

 

DTO는 그저 데이터를 하나하나 일일이 나열해서 전달하는게 불편해서 데이터를 하나로 묶어서 보내려고 만들어진 객체 그 이상도 이하도 아닙니다

public class UserDTO implements Serializable {
    private Long id;
    private String name;
    private String email;

    public UserDTO() {}

    public UserDTO(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

 

예를 들어, controller -> service -> repository로 흐르는 구조에서 아래와 같이 구성이 됩니다. 

controller에서 사용자의 요청을 받아 dto로 변환하고 서비스 계층으로 전달합니다. 서비스 계층에서 dto를 받아서 사용자에게 응답으로 반환합니다. 또한, vo는 entity의 일부로 사용되며 jpa 데이터베이스에 저장될 때는 entity에 포함 됩니다.

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO) {
        UserDTO createdUser = userService.createUser(userDTO);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO userDTO = userService.getUserById(id);
        return ResponseEntity.ok(userDTO);
    }
}


@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public UserDTO createUser(UserDTO userDTO) {
        User user = new User(userDTO.getName(), userDTO.getEmail());
        userRepository.save(user);
        return new UserDTO(user.getId(), user.getName(), user.getEmail());
    }

    public UserDTO getUserById(Long id) {
        User user = userRepository.findById(id).orElseThrow(() -> new UserNotFoundException(id));
        return new UserDTO(user.getId(), user.getName(), user.getEmail());
    }
}


public interface UserRepository extends JpaRepository<User, Long> {
}

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;

    protected User() {}

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getters and setters...
}

 

DTO에 관한 오해

DTO는 계층간 프로세스, 계층 간 이동에 사용된다.

  • DTO의 목적은 데이터를 전달하는 것입니다. 그러므로 데이터를 전달하고 싶은 상황이라면 어디서든 사용할 수 있습니다.
  • 따라서 DTO가 어디에서 사용되느냐는 중요하지 않습니다. 데이터 전송이이 필요한 모든 곳에서 사용할 수 있습니다.

DTO는 게터, 세터를 갖고 있다.

  • 게터, 세터 없이도 내부 데이터를 전달할 수 있습니다. 멤버 변수를 public로 선언하는 것입니다.
public class UserCreateRequest {
    
    public String username;
    public String password;
    public String email;
    public String address;
    public String gender;
    public int age;
}

 

자바가 익숙한 개발자라면 본능적으로 멤버 변수가 public로 선언된 것을 참지 못합니다. 왜냐하면 대 부분의 자바 프로젝트가 "모든 변수는 private로 선언되어야 한다" 라는 규칙을 관행적으로 따르로 있기 때문입니다. 이러한 관행은 캡슐화를 지키기 위해 사용됩니다. 즉, 어떤 객체의 속상값을 감춤으로써 직접 접근을 막고 메서드를 통한 간접 접근으로 안전성과 유연성을 확보하려는 것입니다.

그래서 언뜻 보면 관행을 무조건 따르는 것이 좋아 보이기도 합니다. 다음 예시를 볼께요.

@Getter
@Setter
public class UserCreateRequest {

    private String username;
    private String password;
    private String email;
    private String address;
    private String gender;
    private int age;

}

 

위 코드가 public로 선언한 코드와 어떤 차이가 있을까요? 결론은 차이가 없습니다. 세터가 남발되어 있기 때문에 사용자는 세터를 이용해 데이터를 언제든 접근 수정할 수가 있습니다. 이 코드를 작성해서 얻을 수 있는 가치는 아무것도 없습니다. 그렇다고 public 선언을 남발하라는 의미는 아닙니다. 필요에 따라 public 선언을 활용할 수도 있다는 의미입니다. 예를 들면 다음과 같은 코드를 작성해 DTO로 활용할 수 있을 것입니다.

public class UserCreateRequest {

    public String username;
    public String password;
    public String email;
    public String address;
    public String gender;
    public int age;

    @Builder
    public UserCreateRequest(@JsonProperty("username") String username,
                              @JsonProperty("password") String password,
                              @JsonProperty("email") String email,
                              @JsonProperty("address") String address,
                              @JsonProperty("gender") String gender,
                              @JsonProperty("age") int age) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.address = address;
        this.gender = gender;
        this.age = age;
    }
}

 

이 코드에서 데이터터를 JSON 같은 형식으로 직렬화/역직렬화할 수 있으며, 최초에 데이터가 할당된 이후 변경이 불가능하게 해서 데이터를 보호하고 있습니다. 게터와 세터는 DTO를 정의하는데 필수 조건이 아닙니다. 데이터를 전달한다는 본연의 임무를 다한다면 DTO라고 볼 수 있습니다. 그리고 데이터는 public로 선언돼 있더라도 데이터를 주고 받을 수 있습니다. 

 

그렇다고 모든 멤버를 public로 지정하라는 의미는 아닙니다. 예를들어, 사용자의 이메을 주소를 가져오기 위해 user.getEmail() 메서드를 호출하는 것과 user.email 을 호출하는 것은 다릅니다. 왜냐하면 emal은 속성에 의존하는 것이도 getEmail()은 행동에 의존하는 것이기 때문입니다. 그러므로 캡슐화의 주요 목표중 하나인 정보 은닉을 달생하기 위해서라도 모든 멤버는 private로 선언하되 일부만 getter를 제공하는 방식으로 가는 것이 유리합니다.

 

 

DTO는 데이터베이스에 데이터를 저장하는 데 사용되는 객체이다.

 

DTO에 관해 널리 퍼져 있는 오해는 DTO를 데이터베이스에 데이터를 저장하고 불러오는 데만 사용하는 객체로 알고 있는 것입니다. 이런 오해가 퍼지게 된 이유를 추측하지만 아마도 DTO에서 언급하는 데이터를 데이터베이스로 착각하고 있기 때문입니다.

 

하지만 당연하게도 그렇지 않습니다. 데이터라는 말은 컴퓨터 공학 어디서든 사용되는 개념입니다. 말 그대로 데이터를 전송하기 위한 객체입니다.  API 통신에 사용되는 요청 본문, 응답 본문을 받는 데 사용되는 객체도 DTO고, 데이터를 데이터 베이스에서 불러오고 저장하느 데 사용되는 객체도 DTO입니다. 

 

 

'개발관련 이것저것' 카테고리의 다른 글

고수준 & 저수준  (0) 2024.05.24
SOLID  (0) 2024.05.23
get vs find  (0) 2024.05.23
제네릭 명명 관례  (0) 2024.05.08
String Join 문자열 합치기  (0) 2024.04.09