개발 공부/Java & Spring

DTO vs Map 장점과 단점은?

빵다희 2024. 8. 14. 20:56

💡 주제 

클라이언트에서 서버, 컨트롤러에서 서비스 등 계층 간 데이터를 전송 하기 위해 주로 DTO 또는 Map을 사용한다.

각각의 개념과 장단점, 사용방법에 대해서 정리해본다.


📦 DTO 

  • DTO는 Data Transfer Object의 약어로 계층 간 데이터 전송을 위해 설계된 객체다.
  • DTO는 주로 데이터베이스와 애플리케이션의 비즈니스 로직, 또는 애플리케이션과 클라이언트 간의 데이터 교환을 간소화하는 데 사용된다.
  • DTO는 일반적으로 데이터 필드만 포함하고, 비즈니스 로직은 포함하지 않는다.
  • 각 필드에 대한 getter와 setter 메서드를 제공하여 데이터의 접근을 쉽게 한다. (나는 중간에 데이터가 변경되지 않도록 setter를 추가하지 않는 편이다.)

👍🏻 장점

  1. 계층 간 데이터 전송 단순화
    • DTO는 데이터베이스와 비즈니스 로직, 또는 클라이언트와 서버 간의 데이터 전송을 간소화한다. 필요한 데이터만 포함하여 전송하므로, 불필요한 데이터 전송을 방지할 수 있다.
  2. 유지보수성 향상
    • DTO를 사용하면 데이터 구조를 명확하게 정의할 수 있어, 코드의 가독성이 높아지고 유지보수가 용이하다. DTO의 구조가 변경되더라도, 해당 DTO를 사용하는 코드에서만 수정하면 된다.
  3. 데이터 무결성
    • DTO는 데이터의 유효성 검사를 쉽게 적용할 수 있다. 유효성 검사를 통해 잘못된 데이터가 시스템에 들어오는 것을 방지할 수 있다.
  4. 직렬화 용이
    • DTO는 JSON, XML 등 다양한 형식으로 직렬화할 수 있어, API 응답으로 쉽게 사용할 수 있다.
  5. 보안성
    • DTO를 사용하면 필요한 데이터만 클라이언트에게 전송하므로, 민감한 데이터 노출을 줄일 수 있다.

👎 단점 

  1. 추가적인 복잡성
    • DTO를 정의하고 관리하는 데 추가적인 유지보수가 필요하다. 특히 간단한 애플리케이션에서는 DTO가 오히려 불필요한 복잡성을 초래할 수 있다.
  2. 성능 오버헤드
    • 데이터 변환 과정(예: 엔티티를 DTO로 변환하는 과정)에서 성능 오버헤드가 발생할 수 있다. 대량의 데이터를 처리할 때는 이 점을 고려해야 함.
  3. 데이터 동기화 문제
    • DTO와 엔티티 간의 데이터 동기화가 필요할 수 있으며, 이 과정에서 버그가 발생할 가능성이 있다. DTO의 구조가 변경되면 관련된 모든 코드에서 수정이 필요할 수 있다.
  4. 비즈니스 로직 부재
    • DTO는 단순히 데이터 전송을 위한 객체이므로, 비즈니스 로직을 포함하지 않는다. 따라서 DTO를 통해 데이터의 의미나 처리 방법을 이해하기 어려울 수 있다.

🔍 사용 예시

1. DTO 클래스 정의

  • 나는 보통 요청 데이터를 받는 DTO와 처리 후 응답 데이터를 담는 DTO를 별도로 만드는 편이다.
  • 유효성 검사를 위한 어노테이션을 추가할 수 있다.
/* 요청 데이터 dto */
@Getter
public class RequestUserDTO { 

    @NotBlank(message = "Name cannot be empty")
    @Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
    private String name;

    @NotBlank(message = "Email cannot be empty")
    @Email(message = "Email should be valid")
    private String email;
}

 

/* 응답 데이터 dto */
@Builder
public class RequestUserDTO { 
    private long id
    private String name;
    private String email;
}

 

2. 컨트롤러에서 DTO 사용

  • @Valid 어노테이션을 사용하여 요청 본문에 대한 유효성 검사를 수행한다. 유효성 검사에 실패하면, Spring은 자동으로 MethodArgumentNotValidException을 발생시킨다.
@RestController
@Validated
public class UserController {

    @Autowired
    private UserService service;

    @PostMapping("/users")
    public ResponseEntity<ResponseUserDTO> createUser(@Valid @RequestBody RequestUserDTO userDTO) {
        // 유효성 검사를 통과한 경우, 사용자 생성 로직 수행
        return ResponseEntity.ok(service.save(userDTO));
    }
}

 

3. 서비스에서 DTO 사용

  • dto를 엔티티로 변환해서 db와 통신하고 또 그 결과 값을 다시 엔티티로 변환하여 return한다.
@Service
public class UserService {

    public ResponseUserDTO save(RequestUserDTO dto) {
    
        // 데이터베이스에 사용자 정보를 저장하는 로직
        UserVo vo = UserVo.from(dto);
        UserVo resultVo = repository.save(vo);
        ResponseUserDTO resultDto = UserVo.to(resultVo);
        
        return resultDto;
    }
}

 


📚 Map

  • Map은 데이터의 키-값 쌍을 저장하는 컬렉션이다.
  • 자바에서는 Map 인터페이스를 통해 다양한 구현체(예: HashMap, TreeMap, LinkedHashMap)를 제공하여 데이터를 효과적으로 관리할 수 있다.
  • 계층 간 이동 시에는 주로 데이터 전송 및 변환을 용이하게 하기 위해 사용된다.

👍🏻  장점

  1. 유연성
    • Map은 키-값 쌍으로 데이터를 저장하므로, 다양한 데이터 유형을 유연하게 처리할 수 있다. 필요한 데이터만 선택적으로 추가할 수 있다.
  2. 빠른 검색
    • 해시 기반의 구현체인 HashMap을 사용할 경우, 평균적으로 O(1)의 시간 복잡도로 데이터를 검색할 수 있어 성능이 뛰어나다.
  3. 데이터 정렬
    • TreeMap을 사용하면 키를 기준으로 데이터를 자동으로 정렬할 수 있어, 정렬된 데이터를 필요로 하는 경우 유용함.
  4. 전송이 간단함
    • dto는 요청과 응답시에 엔티티로 변환하거나, 엔티티로 부터 변환하는 과정이 있지만 Map은 변환없이 계층마다 바로바로 사용가능하다. 

👎 단점

  1. 키 중복 문제
    • Map은 키의 중복을 허용하지 않으므로, 동일한  키로 여러 값을 저장할 수 없다. 이로 인해 데이터 덮어씌워지는 등 데이터 관리가 복잡해 질 수 있다. 
  2. 데이터 불투명성
    • Map 안에 어떤 데이터가 들어 있는지 알 수 없어, 모든 계층을 확인해야 하는 어려움이 있다. 이로 인해 디버깅이 복잡해질 수 있다.
  3. 변동 감지의 어려움
    • 데이터 저장에 변동이 생겨도 쉽게 알아차리기 어려워, 데이터의 일관성을 유지하기 힘든 상황이 발생할 수 있다.
  4. 키 의존성
    • 정확한 키 값을 알고 있어야만 값을 가져올 수 있기 때문에, 키의 관리가 중요하며, 잘못된 키로 인해 데이터 접근이 어려워질 수 있다.

🔍  사용방법

1. 컨트롤러에서 Map 사용

@RestController
@Validated
public class UserController {

    @Autowired
    private UserService service;

    @PostMapping("/users")
    public ResponseEntity<Map<String,Object>> createUser(Map<String,Object> paramMap) {
       
        return ResponseEntity.ok(service.save(paramMap));
    }
}

 

2.서비스에서 Map 사용

  • 요청과 응답 데이터 전송 방식이 Map으로 통일된다면, 예시와 같이 코드가 간단해질 수 있다. 
@Service
public class UserService {

    public Map<String,Object> save(Map<String,Object> paramMap) {
    
        // 데이터베이스에 사용자 정보를 저장하는 로직        
        return repository.save(paramMap);
    }
}

 


📝 마무리

데이터 전송시 DTO와 Map을 사용하는 환경을 모두 경험해본 결과, 나는 개발시에 DTO를 조금 더 선호하게 되었다.

위에서 정리했다시피 Map은 별도 클래스 설계가 불필요하고, 데이터 담기가 용이하다. 

쉽게 넣고 쉽게 갖다쓸 수 있다는 이점에서 아직도 많이 사용되지만 단점 또한 명확하다.

데이터의 일관성을 보장할 수 없고 예상치 못한 데이터가 들어올 수 있어서 내가 만든 서비스가 불안할 수도 있다.

가장 큰 불편함은 어떤 데이터가 넘어오는 건지 알 수 없다는 것이다. 프론트부터 DB까지 찾아봐야할 수도 있다.

 

DTO는 그런 불안함에서 벗어나게 한다.

유효성 검사를 통해 유효한 데이터가 아니라면 컨트롤러까지도 접근하지 못하게 할 수 있다.

정의된 자료형과 필드명으로 어떤 데이터가 담길지 짐작할 수 있다.

이런 설계를 위한 작업이 필요하고 계속 유지보수를 해야하지만 그런 점들을 감안하고도 내가 DTO를 선호하는 가장 큰 이유이다.

728x90
반응형