DTO는 말 그대로 데이터를 전달하기 위한 객체로, 주로 계층 간 데이터 교환에 사용됩니다. 그래서 가능하면 비즈니스 로직은 넣지 않고 getter, setter, toString 정도만 구현합니다.
반면에 VO는 값 자체를 표현하는 객체라서 불변성을 유지하는 것이 중요합니다. 그래서 생성자를 통해서만 값을 설정하고 변경 메서드는 두지 않으며, 값의 동등성을 보장하기 위해 equals와 hashCode를 구현하는 것이 일반적입니다.
즉, DTO는 단순히 데이터를 옮기기 위한 수단이고, VO는 값 자체를 의미 있게 다루기 위한 객체라는 차이가 있습니다.
DTO와 엔티티를 직접 매핑할 때는 몇 가지 주의할 점이 있습니다.
먼저, 엔티티를 외부에 직접 노출하면 영속성 정보나 내부 구조가 드러나기 때문에 보안상 위험할 수 있고, 영속성 컨텍스트에도 불필요하게 영향을 줄 수 있습니다.
그래서 반드시 DTO를 별도로 만들어 검증을 거친 뒤 매핑하는 게 좋습니다.
또 지연 로딩으로 인한 N+1 문제가 발생하지 않도록 fetch join이나 전용 조회 DTO를 사용하는 방식으로 최적화해야 합니다.
마지막으로 업데이트 시에는 허용된 필드만 매핑해서 불변 필드나 식별자가 바뀌지 않도록 주의하는 것이 중요합니다.
의존성 주입은 객체가 필요한 의존성을 직접 생성하지 않고 외부에서 주입받는 개념입니다.
이렇게 하면 객체 간 결합도가 낮아지고, 테스트나 확장성이 좋아집니다.
스프링에서는 보통 생성자 주입, 세터 주입, 그리고 필드 주입 세 가지 방법을 지원하는데, 일반적으로는 생성자 주입을 권장합니다.
왜냐하면 생성자 주입은 의존성을 강제로 주입하도록 강제할 수 있고, final 키워드로 불변성을 보장할 수 있기 때문입니다.
생성자 주입이 권장되는 이유는 여러 가지가 있습니다.
우선 생성 시점에 필요한 의존성을 강제로 주입할 수 있어서 필수 의존성이 누락되는 상황을 막을 수 있고, final을 사용할 수 있어서 불변성이 보장됩니다.
또, 순환 참조가 발생하면 애플리케이션 구동 시점에 바로 오류가 나기 때문에 문제를 빨리 찾을 수 있고, 테스트 코드 작성도 더 수월합니다.