본문 바로가기

프로그래밍/Java

Map과 VO(Value Object)의 해묵은 논쟁과 나의 결론..

프로그래머란 생활을 10여년 가까이 하면서 프로그래머들간에 이런저런 논쟁을 하게 되는 상황을 종종 보게 된다. 논쟁이란 것이 결론이 나는 것도 있고 그렇지 않은 것도 있다보니 진행중인 논쟁은 언제나 있기 마련이다. ORM 방식의 개발과 SQL 방식의 개발중 어느것이 나은가?, 왜 SpringFramework에 종속적이어야 하는가 등등 직간접적으로 몇가지 접하는 논쟁이 있다. 오늘은 그 중 데이터 전달을 Map으로 하는게 나을지, VO로 하는게 나을지에 대해 좀 얘기해보고자 한다.

 

먼저 이 논쟁이 나온 배경에 대해 이해를 할 필요가 있어 이 부분을 설명해보도록 하겠다. 우리가 어떤 엔티티(엔티티는 설계상의 용어이므로 잘 이해가 안되는 분은 클래스라고 생각하시길 바란다)를 구체화한 객체를 전송할때 이런식으로 구성할 것이다. 예를 들어 보자. 사용자가 쓴 게시물 데이터를 전달하는 방법을 생각해보자. 이 게시물은 제목과 내용, 단 두 가지의 항목으로 구성되어 있다. 그러면 우리가 흔히 이 데이터를 특정 함수에 전달해서 이 메소드에서 사용하도록 하고자 할 때 다음의 3 가지중 한 가지 방법을 사용할 것이다.

 

1. 메소드의 파라미터로 정의해서 넘기는법

public void writeBoard(String title, String content){
...
}

writeBoard("aaa", "bbb");

 

2. Map에 담은뒤에 이를 파라미터로 전달

 

public void writeBoard(Map<string, string> param){
...
}


Map<string, string> param= new HashMap<string, string>();
param.put("title", "aaa");
param.put("content", "bbb");

writeBoard(param);

 

3. VO(Value Object)에 담은뒤에 이를 파라미터로 전달

 

public class BoardVO{
     private String title;
     private String content;

     // getter, setter 메소드는 있다고 생각하고 여기서는 생략
}

public void writeBoard(BoardVO param){
...
}

BoardVO param= new BoardVO();
param.setTitle("aaa");
param.setContent("bbb");

writeBoard(param);

 

대강 메소드와 클래스의 정의, 그리고 이를 사용하는 방법으로 3가지 형태를 설명했다. 먼저 1의 방법은 구현은 정말 단순하다. 묶어서 어디다 넣을 필요도 없이 걍 1대1 대응으로 파라미터에 넣으면 되기 때문이다. 그러나 세상에 변하지 않는 것은 없다. 지금 당장 변하지 않는다고 정의가 되었더라도 변할 경우 대응할 여지는 남겨야 하는게 설계다. 우리가 코드 설계 및 구현을 하면서 항상 간과하는것이 있다. 세상에 변하지 않는건 없다. 나는 세상에 변하지 않는거라곤, 사람이든 사물이든 시간이 지나면 어떤식으로든 마지막을 맞이하는 원칙 말고는 확실히 결정된 원칙은 하나도 없다고 생각하는 사람이다. 비즈니스 로직또한 마찬가지이다. 지금 당장의 필요성으로 따지면 제목과 내용만 필요하니 그렇게 말했을지 모른다. 그러나 하루가 지나면 등록자 이름이 필요할 상황이 올수도 있고 1주일이 지나면 등록일이 필요한 상황이 올수도 있으며, 한달이 지나면 답글에 대한 정보가 필요한 상황이 올 수 있다. 우리가 만든 프로그램이 그런 변화가 발생할때 최소한의 코드 변화로 최대한 대처하게 할 수 있는가에 대한 고민을 해야 한다. 그런점에서 봤을때 1번은 정말 옆에 저렇게 개발한 개발자가 있다면 스트레이트 펀치를 날리고 싶은 설계이다. 단순히 생각하면 추가 수정사항이 발생할때마다 함수 파라미터를 늘리면 되지..이럴수도 있을 것이다. 그러나 함수의 정의를 고치면 그것을 사용하는 곳 또한 고쳐야 한다; 파라미터 갯수가 틀려지고 타입이 틀려지는데 그게 정상적으로 동작하겠는가? 더군다나 저 메소드를 호출한 곳이 1군데가 아니라 10군데, 100군데라고 가정해보자. 그러면 우리는 일일이 찾아서 저 함수를 호출한 부분을 고쳐야 한다. 또한 추가되거나 삭제되는 파라미터가 옵션, 즉 반드시 필요한게 아니라 해도 메소드 정의가 바뀌었기 때문에 일일이 찾아서 고쳐줘야 한다.

 

2번과 3번의 경우는 그나마 사정이 나은 편이다. Map이 됐든 VO로 만들든 수정사항이 발생할 경우 수정사항을 필요로 하는 곳에서만 데이터를 셋팅만 해주면 되고 그것을 사용하는 부분은 고칠 필요가 없다. 그런 점에서는 확실히 1번보다는 2번과 3번이 낫다.

 

문제는 2번과 3번, 즉 Map을 사용하는것이 나으냐, 아니면 VO를 사용하는 것이 나으냐..란 점이다. 방법 자체는 흠 잡을때가 없다. 실제로 디자인 패턴을 보면 Map이든 VO든 객체를 값으로 전달하는 것은 하나의 패턴으로 인식되어 얘기되어지고 있다. 머 디자인 패턴 자체를 얘기할려고 하는게 아니라 그만큼 검증은 되어 있다는 점에서 얘기하는 것이다. 2번과 3번 모두 문제는 딱히 없어보이는데도 사람들은 Map이 낫다고 하느니 VO가 낫다고 하느니 이거 가지고 논쟁을 벌이고 있고 아직도 진행중이다. 그럼 왜 우리가 이런 논쟁에 휩쓸리고 있는지 그 배경에 대해 생각 해 볼 필요성이 있다.

 

Map이 낫다고 주장하는 사람의 논리는 이렇다. 요구사항 변경시 수작업을 해야 할 부분이 많다는 것이다. 예를 들어 VO로 할 경우 A라는 VO에서 B 라는 VO로 바꿔야 할 경우 이 부분을 수정하는 것이 연쇄적으로 발생해서 이를 수정하는데 시간이 오래 걸린다는 것이다. 이 부분은 특히 우리나라 SI 환경에서는 민감하다. 왜냐면 우리나라 SI 플젝을 보면 열중 일곱여덟은 오픈하는 1주일전까지도 확정이 안되어있는 사항이 있을 정도로 변화무쌍하다는 것이다. 남은 시간이 얼마 남지 않은 상황에서 추가 변경 상황이 발생할 경우 Map으로의 전달은 대처하기가 쉽다는 것이다.

 

VO가 낫다고 주장하는 사람의 논리는 이렇다. Map의 경우는 put이나 get 메소드의 사용을 일일이 보지 않는 한에는 무엇을 전달할려고 하는지 그 의미를 알수가 없다는 것이다. VO일 경우 소스의 주석이나 변수명만 봐도 어떤 값들이 전달되는지 알 수 있으나 Map의 경우 실제 그 Map이 사용되는 곳을 봐야 알수 있는것이다.

 

양쪽 모두 틀린 얘기는 아니다. 모두 이론적인 얘기가 아닌 현실에서 발생하는 문제를 가지고 이슈제기를 하는 것이니까..나도 이 두 쪽의 의견에는 전적으로 공감은 하는 바이다. 그러나 우리가 앞으로 나가야 할 방향을 생각해야 한다면 나는 VO가 옳다고 생각한다. 이제 이렇게 생각하는 이유에 대해 얘기를 해보겠다.

 

근래에 하이버네이트를 공부하면서 나름 충격을 먹은게 하나 있었다. 하이버네이트 세미나 동영상 중 이런 내용이 있었다. 우리나라 개발자는 프로그램을 만드는데 있어 프로그램을 먼저 설계하는 것이 아니라 프로그램을 통해 만들어지는 값이 저장되는 DB 설계를 먼저 하고 프로그램설계를 그 나중에 한다는 것이다. 난 이걸 보고 정말 충격을 먹었다. 그럴수밖에 없었다. 왜냐면 당연히 했었어야 하는 자기 질문을 이제껏 한번도 안했기 때문이다. 나는 프로그래머고 프로그램을 만드는 사람이면 현실의 개념에 맞춰 프로그램을 설계해야 하는데 언제부턴가 프로그램을 통해 나오거나 또는 이용되는 값이 저장되는 DB에 의존적인 프로그램 설계를 하고 있는것이다. 예를 들어보자. 회원 정보를 보면 이름도 들어가고, 주소도 들어가고, 전화번호도 들어간다. 주소의 경우 우편번호와 우편번호에 매핑되는 주소, 그리고 상세주소 속성이 있게 되고 이를 확장해서 사용할 가능성이 있는 회사 주소란 개념과 집 주소란 개념도 있다. 전화번호도 국번과 번호란 속성이 있으며 이용용도에 따라 핸드폰 번호, 집 번호, 회사번호 등 여러가지가 있다. 그러면 회원정보를 클래스로 설계할때 우편번호를 나타내는 zipcode와 우편번호와 엮어지는 주소인 address1, 상세주소를 의미하는 detailaddress라는 속성으로 구성되는 Address 란 주소를 의미하는 클래스를 만들고 이를 상속한 집주소를 의미하는 HomeAddress, 회사주소를 의미하는 JobAdrdess 이렇게 클래스를 만들어서 회원정보의 멤버필드로 HomeAddress 타입, JobAddress 타입의 멤버변수를 만들어서 설계하는것이 객체지향 방식의 설계이다. 그러나 DB는 이렇게 구현할수가 없다. 여러개의 속성을 하나로 묶어서 표현하게 되는 객체와 부모의 속성을 물려받아 다시 사용하는 상속이란 개념이 없기 때문에 원데이터를 그냥 넣어야 한다. 즉 문자열 타입의 ziipcode 컬럼과 address1 컬럼을 만들어야 한다. 즉 클래스의 속성들을 풀어서 테이블의 컬럼으로 구성해야 하는 것이다. 그러나 이런 구조이기 때문에 클래스와 DB 컬럼이 1대1로 매핑이 되지 않기 때문에 우리가 생각하는 객체지향적인 설계와 DB 구성이 Mismatch가 일어나게 된다. 또 이런 증상은 새로운 속성이 생기거나 기존 속성이 없어질때도 마찬가지로 벌어지게 된다. 그러다보니 결국은 클래스 설계를 DB 컬럼과 매핑이 되는 식으로 설계해서 유지보수 하기 용이하게끔 하고 있다. 즉 별도의 클래스로 따로 빼서 확장이 용이하게 설계할수 있는데도 불구하고 이런 문제땜에 일일이 풀어서 설계하고 있는 것이다.

 

이 얘기를 왜 하냐면 우리가 만드는 것이 무엇인가..에 대한 고민을 전혀 하지 않고 만들고 있기 때문에 그런것이다. 우리가 만드는 프로그램은 현실 생활의 변화에 맞춰 최소한의 변경만으로도 변화에 적응할수 있게끔 만들어야 하는데, 어느 순간부터 단지 DB에서 검색이 잘 되게끔 하기 위한 자료구조 형태에만 포커스를 맞추고 있기 때문이다. 즉 DB중심위주의 프로그래밍이 아닌 현실 위주의 객체지향적인 프로그래밍으로 전환해야겠다 생각하던 시점에 요근래 내가 잘 가는 커뮤니티에서 Map과 VO 논쟁을 겪었다. 그래서 현실 위주의 객체지향적인 프로그래밍 관점에서 낸 결론은 VO가 낫다는 것이다

 

최소한의 변화로 최대한의 수정효과를 거두는것에서 놓고 보면 VO가 단연 좋다. 해당 관심사의 집약체가 VO이기 때문에 바꿔말하면 VO에 대한 수정만 해주면 큰 문제가 없다. 오히려 Map의 경우는 이 수정을 할 경우 제대로 동작하는지 확인할 길이 없다. 버그가 내재되어 있다는 것이다. 여러개의 타입의 값을 전달해야 하기 때문에 Map은 선언 특성상 Value를 Object로 선언할수 밖에 없다.그러나 Object는 최상위 타입이기 때문에 타입 미스매치가 발생하는 상황이 있어도 실제로 돌려보지 않는 한에는 알 수가 없다. 그러나 VO에서는 멤버변수에 대한 구체적인 타입을 정할수 있기 때문에 만약 타입이 바뀔 경우 컴파일러에서 에러가 발생하므로 이를 바로 잡아낼수 있다.또한 반드시 들어가야 하는 값이라면 생성자의 파라미터로 필요한 값을 넣게끔 설계해주면 컴파일러에서 그렇게 맞추지 않았을 경우 에러가 발생하기 때문에 버그없이 구현될수 있다.

 

혹자는 이 점을 들어서 그게 VO의 단점이라고 얘기하는데 나는 그 얘기는 정말 단순한 발상에서 나온거라 생각한다. VO로 구현해서 수정할 경우 이클립스 같은 IDE 툴에서 VO 변경이 되었기 때문에 당연 에러가 쫙쫙 나올것이고 개발자는 그 에러를 고치느라 노가다를 할 것이다. 그러나 이것은 눈에 보이는 명확한 에러를 우리에게 보여주었기 때문에 에러를 수정하면 버그가 내재되지 않는다. 그러나 Map으로 했다고 가정해보자. 값이 들어가는 Value의 타입이 최상위 타입인 Object로 했기 때문에 모든 타입을 다 받아들일수 있어서 당장은 수정해야 할 부분은 없어보일수도 있다. 그러나 눈에은 안보이는 부가적인 Cast 연산이 추가로 발생되며 또한 타입에 대한 부정확성 때문에 버그가 내재될 소지도 크다. 또 만약 필수로 들어가야 하는 항목인데 put을 안했다면 논리적인 잘못이 있으나 컴파일러상에선 오류가 발생할 상황은 아니기 때문에 이 또한 버그의 소지를 안게된다

 

그리고 이 문제는 VO를 수정하는 상황이 없으면 이럴일도 발생하지 않는다. 위에서 잠깐 우리나라 SI 개발환경에 대해 언급을 했는데, 그런 개발환경땜에 문제가 없는 개발방법을 문제가 있다고 생각하는 사람들을 보면 정말 한숨뿐이 안나온다. 만약 설계사항이 모두 도출되었고(여기서는 변경의 여지가 있다..라는 얘기까지 나와서 변경이 될 경우의 설계까지도 반영되었다고 전제한다)..그에 맞춰 설계했다면 VO는 효율적이면 효율적이지 절대 비효율이 아니다. 확장성이 용이한데다가 관심사가 집중되기 때문에 여러군데 손을 댈 필요가 없다. 또한 타입이 분명하기때문에 적어도 타입관련 버그가 있을수가 없다. Collection 타입 조차도 Generic의 도입으로 안에 무슨 타입이 들어간다는걸 사전에 정의할 수 있기 때문에 컴파일러에서 조차 타입관련 에러를 잡아낼수 있다.

 

그러나 Map의 경우 값은 모든 타입을 다 받아들일수 있게끔 최상위 타입인 Object로 값 부분의 타입을 해야 한다. 물론 모든 타입을 String으로 할수도 있을것이다. 그러나 Object가 됐든 String이 됐든 이럴 경우 부가적인 Castring이 필요하다. 계산을 할려면 숫자형으로 변경해야 하며 변경을 하지 않을 경우 타입이 불분명하기 때문에 동작에 있어서 버그가 존재할 여지는 충분히 있다. 두리뭉실한 타입은 개발에서는 편할수 있으나 이것도 되고 저것도 되는 식의 타입은 자칫 동작에 있어서 오류를 가질 소지가 크기 때문이다. 이렇게 쓰면 Casting 작업이 비효율적인 작업이라고 오해하는 사람이 있겠으나 나는 비효율적인 작업이라고 하는게 아니라 불필요한 작업이라고 말하고 있는것이다. 타입을 명확하게 정의하면 이 작업이 필요가 없는데 타입을 명확하게 정의하지 않았기 때문에 이 작업이 필요하게 되는 것이다. VO로 개발하면 이런 작업이 필요가 없는데 왜 불필요한 작업을 하나 더 거치게 해서 전반적으로 비효율적인 작업으로 이르게끔 하냐는 것이다.

 

이런 조금만 생각해보면 되는것을 단지 우리나라 SI 환경에는 맞지가 않다고 VO를 안좋다고 말하는 사람들을 보면 답답하다. 개발을 하다보면 그런 현실적인 문제땜에 Map을 선택해서 개발할수도 있다. 현실적인 문제땜에 Map을 선택할수 밖에 없었던 것에 대해서는 머라 하진 않겠다. 그러나 그 방법이 옳다고 말할수는 없는것이다. 이건 마치 정당방위로 어쩔수 없이 사람을 죽였는다. 사람을 죽이는 행위는 올바른 것이다 와 다름없는 논리다. 상황땜에 그렇게 개발한것을 가지고 왜 그것을 VO로 개발하면 안되는거라고 주장하는가? 비정상적인 환경에서 도구가 쓰였다고 애초에 문제가 없는 도구를 문제삼을수는 없는 것이다. 설계를 할때 요구정의를 사전에 미리미리 결정을 짓고 충분히 검토하고 구현하면 그럴 일이 없는데 단지 그런 환경이 아닌 우리나라여서 그래서 VO를 이용한 개발방법은 문제가 있다는 식의 논리는 맞지가 않다는 것이다. 혹자는 왜 환경탓을 하냐고 하지만 이것은 환경탓이다. 그런 환경이라면 바꿔야 하는게 맞는거지 그런 환경에서까지 버그 없이 완벽한 개발방법은 존재하지 않기 때문이다. 

 

혹자는 이걸 보고 VO를 만능으로 보는것 아니냐고 생각하는데 나는 만능이라고 보진 않는다. 다만 현재 나와 있는 개발방법에서 버그가 없고 전달하는 의도가 분명하며 관심사에 변화가 발생했을시 최소한의 대응을 할수 있는 것을 VO라 보기 때문이다. 만약 VO보다 더 좋은 방법이 있음 그걸로 갈아 탈 것이다. 그러나 VO와 Map을 놓고 비교해보면 아직은 VO가 더 우선순위에 있다고 말하고 싶다. 그리고 만약 VO가 안좋다고 한다면 왜 대형 프로젝트에선 UML로 설계해서 나온 엔티티를 Map으로 구현할탠데 왜 그렇지 않은가? 이는 VO로의 구현 방법이 객체지향적인 설계 방법에서도 옳은 구현이기 때문이다. 관심사의 집중, 최소한의 변화로 최대의 효과..현재로썬 그걸 잘 구현한 방법은 VO라고 본다.