java bean mapper와 DTO
Overview
spring, jpa 기반으로 개발할때 도움이 될 수 있는 java bean mapper 라이브러리를 소개합니다.
아래와 같은 간단한 JPA Entity 객체를 가지고 설명합니다.
그러면 다음 그림과 비슷한 결과를 응답하게 됩니다.
이럴 경우에 발생하는 순환 참조 문제같은 몇가지는 jackson json 라이브러리가 해결해주기도하지만 일반적으로 아주 작은 프로젝트가 아니라면 추천할만한 방식이 아닙니다. JPA Entity 도메인 데이터와 api 응답 데이터간에 생명주기가 틀리기 때문입니다.
데이터 생명주기
프로젝트의 초기에는 요구사항에 잘 맞춰서 데이터베이스가 설게되기 때문에 이렇게 Entity 를 바로 응답해도 큰 문제가 안되지만 프로젝트가 실제 운영을 시작한후에 발생하는 요구사항을 위한 기능추가가 반복되다보면 처음 설계된 데이터베이스 구조로는 수용이 불가능한 경우가 발생하기 시작합니다. 이때부터 다양한 코딩 기법을 동원하여 문제를 해결하게 되는데, 데이터베이스를 통한 물리적 관계(relation)를 이용하지 않고 논리적인 관계 즉 프로그램 코드 상으로만 연관관계를 형성하게 되는 데이터들이 발생하게 됩니다.
이렇게 처리할 수 밖에 없는 이유는 관계형 데이터베이스는 일반적으로 한번 만들어져서 데이터가 쌓이기 시작하면 큰 수준의 변경이 거의 불가능하기 때문입니다. 데이터베이스는 한번 서비스를 시작한 이후에는 거의 바뀌지 않지만 서비스의 요구사항은 계속해서 추가, 변경을 반복하면서 이애 대응하는 api 응답 데이터 포맷은 더 짧은 주기에 변할 수 있습니다. 그래서 JPA Entity 객체를 바로 api 응답으로 사용하는 것은 적절하지 않습니다.
이렇게 생명주기가 다른 데이터베이스 모델과 api 응답 데이터 간에 사이를 매꿔주는 역할을 하는 것이 바로 DTO(data transfer object) 입니다. JEE(java enterprise edition) 에서 소개되었으며 VO(value object) 라고도하는 DTO 는 model -> service -> controller 로 이어지는 MVC 어플리케이션에서 계층간에 데이터를 전달할 목적으로 사용합니다. 간단하게 말하면 DTO는 로직을 포하하지 않고 데이터의 전달만을 목적으로 사용하는 객체입니다.
도메인 모델 데이터를 DTO를 통해서 전달하게 되면 원래의 데이터에 넣고 빼거나 조합거나, 혹은 또 다른 데이터와 병합하거나 하는 등의 조작을 통해 데이터 포맷을 효과적으로 조정할 수 있습니다.
서버 api 의 응답 데이터를 DTO를 이용해서 만들게 되면 도메인 모델과 비지니스 로직간의 생명주기 간극을 훌륭하게 매꿀 수 있습니다.
DTO 사용하기
위의 Entity 에 대해서 아래와 같은 DTO 를 만들고 어떻게 사용하는지 확인해보겠습니다.
1. Brute-force 방식 DTO 객체 사용
먼저DTO 객체를 가장 단순게 사용하는 코드를 한번 보겠습니다.
위와 같이 작성후 실행해보면 아래와 같은 응답을 받게 됩니다.
jpa entity 에는 있지만 DTO 에서는 정의 하지 않은 필드가 응답에 포함되지 않았습니다. 이와 같이 일차적으로 DTO는 entity 객체에서 불필요한 정보를 제외하고 값을 전달하는 마스킹의 역할을 하게 됩니다.
위 코드는 잘 작동하지만 이런 방식은 지루한 반복작업이 필요한 코드가 만들어질 뿐 아니라 어플리케이션의 유지보수 관점에서도 문제가 있습니다. 만약 새로운 기능 개발로 인해 Entity 에 변경이 생기고 DTO 도 같이 수정 되야 한다면 위 코드도 추가적인 필드 매핑 작업이 필요 합니다. 또 만약 이런 매핑코드가 여러곳에 존재한다면 각 코드들을 모두 추적하여 수정을 해야 합니다. 이런 방식은 유지보수과정에서 오류를 유발할 가능성이 높고 개발자를 피곤하게 만듭니다.
2. Bean utils 를 사용
다음으로 생각해볼 수 있는 방법은 Bean util 객체를 사용하는 것입니다. Apache commons 나 spring 에 BeanUtils 객체가 있습니다. Source 객체에 있는 필드들의 값을 target객체의 필드에 똑같이 set 해주는 역할입니다. 이를 이용한 코드는 아래와 같습니다.
BeanUtils가 객체를 분석해서 동적으로 값 복사를 해주기 때문에 코드가 단순해졌습니다. 다만 이런 BeanUtil 들은 기본적인 기능만을 가지고 있어서 문자, 숫자같은 단순한 값복사는 잘되지만 그이상의 복잡한 상황에서는 제대로된 값 복사가 되지 않습니다. 위 코드를 실행한 결과는 아래와 같습니다.
위와 같이 String, Long 필드는 복사가 되었지만 MenuGroup Entity 값이 MenuGroupDto 로는 복사되지 않았습니다. 이름이 같아도 객체 타입이 다른 상황등은 해결해주지 못합니다.
3. Java bean mapper 사용
Java bean mapper 라이브러리는 java 객체간에 값을 전달하는 기능을 체계적으로 관리해주는 솔루션입니다. 찾아보면 여러 종류의 mapper 라이브러리가 있는데, 이글에서는 modelmapper (http://modelmapper.org) 라는 라이브러리를 사용합니다.
사용하는 코드를 한번 보겠습니다.
한줄로 간단하게 처리가 가능하고 BeanUtils 가 처리해주지 못하는 여러가지 상황에 대해서 값 복사를 해줍니다.
위와 같이 MenuGroup 객체의 값이 다른 타입인 MenuGroupDto 객체로 복사되었습니다.
이렇게 bean mapper가 지능적으로 값복사를 해줍니다.
대략적인 기능을 정리해보겠습니다.
필드의 이름이 같다면 다른 타입의 객체로도 매핑 가능
객체안에 중첩되어 있는 다른 객체나 List 같은 collection객체의 값을 계속 따라들어가면서 매핑 가능
설정을 통해 다양한 매핑 조건 설정가능
값이 null 인 필드 복사 여부 설정
특정 필드 매핑여부 결정
지정한 다른 이름의 필드로 매핑
Access levle(public, protected, private..) 에 따라 매핑 여부 결정
Custom 변환기를 통해 원하는 형태로 매핑 가능
결론
프로젝트 진행 초기에 DTO를 만들면 도메인 Entity 객체와 거의 비슷한 클래스를 만들게 되어 지루하게 객체 생성을 반복하고 도메인 객체와 같은 값을 그대로 내보내게되는 경우가 대부분입니다. 때문에 DTO 객체의 효용성에 대해서 의문을 가지는 경우를 종종 보았습니다.
그러나 DTO객체 사용의 진가는 서비스 운영중에 확인이 됩니다. 앞서 말한데로 운영중인 서비스의 데이터 베이스구조를 크게 바꾸기가 어려운데, 비즈니스 요구사항은 보통개발 설계상 애로사항이나 적정 일정등이 고려되지 않고 결정됩니다. DTO가 이런 문제들을 해결하는데 도움을 줄것입니다. 물론 이런 데이터 조작이 많아 질 수록 프로그램의 최초 설계가 점점 깨져갈것입니다. 저는 이것도 프로그램의 생명주기상 자연스런 과정이고 개발자가 밤을 세는것 보다는 훨씬 낫다고 생각합니다.
참조
Last updated