(Spring) MVC Layered Architecture - Map 보다 Data Class 사용해야 하는 이유
Map과 data class는 애초에 용도와 목적이 다르다. Map은 data class 처럼 쓸 수는 있지만, 그렇게 쓰는게 Map의 올바른 사용법이라고 할 수는 없다.
Data Class 대용으로 Map을 사용할 때의 단점
- 타입 정보가 유실되어 type safe 하지 않다는 점. 이게 가장 큰 단점이자 본질적인 단점이다.
- 꺼낼 때 형변환 필요해서 번거롭다.
- key 지정을 String으로 하다 보니 잘못된 참조에 대한 컴파일 타임 체크가 불가능하다. fragile.
- 어떤 필드가 어디서 사용되고 있는건지 IDE의 추적 기능 도움을 받을 수 없어 일일히 따라가보아야 한다. 리팩터링이 굉장히 어렵다.=유지보수가 어렵다. (e.g., 이 필드 삭제 해도 되는걸까?)
Map을 Domain Model로 쓴다면?
- Domain Model에 대해서
- 당연히 안된다. Map을 Domain Model로 쓴다는 것은 시스템에 자폭 기능을 넣는 것과 같다.
- 이렇게 오남용된 Map은 자살 특공대가 되어 언제 어떤 형태로든 반드시 당신의 발목을 잡을 것…
Map을 DTO로 쓴다면?
십수년 전에 논쟁이 있었던 주제
이희승 님도 등장하는데, 십 몇년 전 개발 환경에서 이 정도의 인사이트라니… 역시 놀랍다.
현대 애플리케이션 개발 환경에서 DTO로 Map이 아니라 data class를 써야하는 지배적인 이유를 하나만 뽑으라면 바로 애너테이션 이다.
예를 들어 Controller Parameter(presentation DTO)로 Map을 사용하는 것 대비 별도의 Data Class를 만들어 받으면 아래와 같은 장점이 있다.
- type safety 같은 기본적인 data class의 장점들에 더해서,
- validation, Json, swagger 등 presentation layer에서 필요한 애너테이션 사용 가능
- 클라이언트로부터 어떤 필드를 받는지 한 눈에 파악 가능
- searchParam을 Map으로 받고, 이게 data layer까지 스쳐 지나가는 경우를 예로 들면, select 문 까지 따라가야 어떤 파라미터가 Map으로 넘어오는지 확인이 된다.
- 게다가 Map이 스쳐 지나가는 layer 마다 어떤 필드는 사용하고 어떤 필드는 그냥 통과한다면?
- 1개의 Object가 여러 layer를 스쳐 지나가도록 설계하는 것 자체가 좋은 패턴이 아니지만 Map을 사용하면 파악하기가 더욱 어려워진다.
반면 presentation layer 응답 DTO로 Map을 쓰는 것은?
- Controller에서 응답 직전에 Map으로 바꿔서 응답하는 식으로 Map 사용 구간을 최소화하면 유지보수에 크게 문제가 되지 않을지도 모른다.
- 그러나 지저분해지는 컨트롤러 로직, 재사용성, 통일성, 대칭성이라는 가치를 생각해보면 여기서 굳이 Map을 사용하는 것은 득보다 실이 더 큰 선택으로 보인다. (그리고 역시 Map은 swagger도 못쓴다.)
그럼에도 왜 DTO나 Domain Model에 Map을 쓰는 패턴이 많이 보이는가?
- data class 만드는게 꺼려지기 때문이다. 새로운 클래스를 추가한다는게 심적인 부담으로 다가오는 경우가 있다.
- Map은 유연하니까, 상대적으로 light하다는 느낌을 준다. data class를 만들어야 하는지에 대한 확신이 없을 때, 어떻게 만들어야 하는지에 대한 단서가 없을 때 Map이 굉장히 매력적으로 보일 수도 있다.
- 클래스가 많아서, 파일이 많아서 지저분해질거 같아서 Map을 사용하겠다는 사람도 있다.
- => 요청/응답 전용 data class들만 따로 패키지를 만들어서 거기에 몰아넣고 열어보지 않으면 된다.
data class와 Map은 그 용도와 목적이 다르다. 위와 같은 사유로 data class와 Map을 선택해서는 안된다.
data class 만드는 것을 주저 할 필요가 전혀 없다.
Map을 사용하는게 적절한 상황
그렇다면 Map은 절대로 사용하면 안되는 요소인가? => 그건 아니다. Map을 사용하는게 적절한 상황, 사용해도 나쁘지 않은 상황도 분명 존재한다. (Map의 존재 의의에 맞는 상황)
- 객체의 사용 범위가 [한 메서드 내, private 메서드 간, local scope 등] 작은 범위로 제한되는 경우
- 예를 들어 external Response 수신 할 때. 물론 아예 받을 때 부터 DTO로 받을 수 있으면 더 좋겠지만 상황이 여의치 않은 경우, 일단 Map으로 받은 다음 바깥으로 전달하기 전 DTO로 변환하여 던진다면 크게 문제 되지 않는다.
- *** 어차피 Response 받을 때 jackson이 먼저 Map으로 받고 -> 그 다음 Object로 매핑해주는 것이기 때문에, user code 단에서 jackson이 하는 일을 직접 한다고 생각해도 좋다.
- 단순히 두 가지를 함께 return 하기 위한 목적으로 작은 범위에서 사용하는 경우
- 그러나 이 것도 Pair나 Tuple에 비해서 type safe하지 않아서 별로 선호하지는 않는다.
- 하지만 사용하다 보면 점차 Map을 범용적으로 사용하게 될 수 있으므로, 제어 가능한 작은 범위에서만 사용하게끔 주의해야 한다.
- 예를 들어 external Response 수신 할 때. 물론 아예 받을 때 부터 DTO로 받을 수 있으면 더 좋겠지만 상황이 여의치 않은 경우, 일단 Map으로 받은 다음 바깥으로 전달하기 전 DTO로 변환하여 던진다면 크게 문제 되지 않는다.
key-value store로서 기능하는 경우. (Map 본래의 목적)
- 저장 되어 있는 key 또는 value로 iter 돌아야 하는 경우가 있는데, 이는 Map을 사용하기 적합하다는 시그널 일 수 있다.
This post is licensed under CC BY 4.0 by the author.