[Spring] DTO

Donghoon Jeong·2023년 11월 7일
0

Spring

목록 보기
14/15

DTO (Data Transfer Object)

DTO(Data Transfer Object)란 데이터 전송 객체를 의미하는 패턴으로 계층 간 데이터 전송을 위해 도메인 모델 대신 사용되는 객체입니다.

❓ 데이터를 교환할 때 Entity 대신 DTO를 이용하는 이유는 뭘까?


DTO를 사용하는 이유

  • 데이터 보호 : DTO는 민감한 정보의 노출을 방지할 수 있습니다. 예를 들어, 사용자의 모든 정보를 담고 있는 객체 대신에, 필요한 정보만을 담고 있는 DTO를 통해 데이터를 전송하면, 민감한 정보가 노출되는 것을 방지할 수 있습니다.

  • 유연한 처리 : 엔티티는 일반적으로 DB 구조와 밀접하게 연관되어 있지만 DTO는 비즈니스 요구사항에 맞게 구성될 수 있어 유연합니다.

  • 요구사항 반영 : View와 통신하는 DTO 클래스, 예를 들어 RequestDTO, ResponseDTO는 요구사항에 따라 자주 변경될 수 있습니다. 
    따라서 Entity 클래스와 분리하여 관리해야 합니다. 

  • 데이터 무결성 유지 : Entity 객체를 직접 사용하면 데이터 무결성을 위협받을 수 있습니다.
    특히, 여러 스레드가 동시에 같은 Entity 객체를 참조하거나, 클라이언트가 Entity 객체를 자유롭게 변경할 수 있을 때 문제가 될 수 있습니다.
    이런 상황에서 DTO를 사용하면, 원본 Entity 객체를 보호하고 데이터의 일관성과 무결성을 유지할 수 있습니다


예시

Entity 생성

@Entity
@Getter
@Setter
@NoArgsConstructor
public class User {

    @Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String email;
    private String address;
    private String phone;
    
}

Controller 생성

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
	User user = userService.findById(id);
    return ResponseEntity.ok().body(user);
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    return ResponseEntity.ok(userService.saveUser(user));
}

다음과 같이 Entity를 사용 할 경우, 클라이언트에게 노출되어서는 안되는 정보인 비밀번호와 개인 연락처 정보까지 넘어가게되고, 이와 같이 클라이언트가 User 객체를 직접 전송하면, 클라이언트는 필드 값을 자유롭게 변경할 수 있습니다.

이는 데이터의 무결성을 해칠 수 있습니다.

따라서, 클라이언트에게 전달할 정보를 담는 UserDTO를 아래와 같이 만들 수 있습니다.

DTO 생성

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {

    private String username;
    private String email;
    
}

Controller 생성

@GetMapping("/users/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
    User user = userService.findUserById(id);
    
    UserDTO userDTO = UserDTO.builder()
                             .username(user.getUsername())
                             .email(user.getEmail())
                             .build();

    return ResponseEntity.ok(userDTO);
}


@PostMapping("/users")
public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO) {
    User user = User.builder()
                .username(userDTO.getUsername())
                .email(userDTO.getEmail())
                .build();

    user = userService.saveUser(user);

    return ResponseEntity.ok(userDTO);
}


위 Controller의 조회 메소드에서는 UserDTO를 반환합니다. 이렇게 DTO를 사용하면, 클라이언트와의 통신에서 민감한 정보를 보호하고, 필요한 데이터만을 송수신할 수 있습니다.


DTO Validation

Validation이란 검증이란 의미로, DTO Validation은 DTO 값이 우리가 원하는 형태로 잘 들어왔는지 확인하는 것을 의미합니다.

내부에서 서버의 예기치 않은 동작을 제어하기 위해서 데이터를 전송할 때, 아무런 값이나 가져와서 저장하지 않고 입력 값이 유효한 값인지 확인후 전송을 해야합니다.

위 작업을 DTO 객체에 대한 Validation을 통해 수행 할 수 있습니다.

이제 DTO Validation을 적용하는 방법에 대해서 설명하도록 하겠습니다.


의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-validation'

DTO 생성

@Getter
@Setter
public class UserDTO {

    @NotEmpty
    private String username;

    @NotEmpty
    private String email;
    
}

Controller 생성


@PostMapping("/users")
public ResponseEntity<UserDTO> createUser(@RequestBody @Valid UserDTO userDTO) {
	User user = User.builder()
    			.username(userDTO.getUsername())     
                .email(userDTO.getEmail())
                .build();

	user = userService.saveUser(user);

	return ResponseEntity.ok(userDTO);
}
    

해당 DTO를 사용하는 Controller에서 @Valid를 사용하겠습니다.

Controller에서 DTO를 그냥 매개값으로 사용한다고 해서, 값의 검증이 이루어지는 것은 아닙니다. 값의 검증이 이루어지기 위해서는 @Valid 어노테이션을 이용해서 값을 검증하겠다고 미리 알려주어야 합니다.

GlobalExceptionHandler 생성

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> dtoValidation(final MethodArgumentNotValidException e) {
    
        Map<String, String> errors = new HashMap<>();
       
       e.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(errors);
    }

}

해당 유효성 검사 실패 시, 검증 실패를 처리할 수 있도록 예외를 처리해야 합니다.

해당 예외처리 로직을 구현하는 GlobalExceptionHandler를 다음과 같이 작성하였습니다.

profile
정신 🍒 !

0개의 댓글