엄범

* Model은 보통 Business layer에서 사용하는 Domain Model을 의미한다.

* 기타 layer에서 사용하는 Data Class는 Data Class라고 불렀다.

 

Controller에서 Parameter : Map으로 받는 것과 별도의 Data Class를  만들어 받는 것?

  • 별도 Data Class를 만들어 받으면 애너테이션 사용 가능
    • `` @Valid`` 가능!
  • 좀 더 type safety 하다
  • 클라이언트로부터 어떤 필드를 받는지 한 눈에 파악 가능
    • 사실 이 것 때문에 웬만하면 만드는 것이 좋음.
    • searchParam을 Map으로 받는 경우를 예로 들면, select문 까지 따라가야 어떤 파라미터가 Map으로 넘어오는지 확인이 된다.
      • 유지보수하기 나쁨.

 

Conrtoller에서 Response : Map을 내려주는 것과 별도의 Data Class를 만들어 받는 것?

admin으로 부터의 요청은 a필드가 필요한데, service로부터의 요청은 민감 정보라 a가 없어야 한다.

또는 그 반대 상황이거나, service로부터의 요청은 추가적인 필드 정보가 더 필요하다거나.

등등 다양한 상황을 마주하게 되는데

 

Domain Model Class 하나로 

  • business layer
  • admin요청 응답
  • service요청 응답

모두 커버하기에는 애매한 경우가 종종 생긴다.

 

이런 문제를 해결하기 위해 떠오르는 방법은 보통 3가지인데,

  1. 컨트롤러에서 삭제하고 싶은 민감 정보 필드에 null 할당
    & 추가적인 필드를 응답으로 내려주기 위해 Model Class에 필드 추가
    • 단점은 같은 Model Class가 내려가는데 어느 요청에는 필드가 있고, 어느 요청에는 없고... 이게 애매하다.
    • 추가 field가 필요한 경우 모델 클래스에도 추가해줘야 해서, 모델 클래스가 계속 비대해질 수 있다는 것도 문제점.
  2. 그냥 `` Map<>``으로 내려주는 방법
    • 비즈니스 로직에서는 계속 모델 클래스로 들고다니다가, 컨트롤러에서 응답 직전에만 Map으로 바꿔서 내려주는 식으로 Map 사용 구간을 최소화하면 유지보수에 크게 문제가 되지는 않음.
    • 추가 field가 필요한 경우 그냥 Map에 추가하면 되고. 삭제가 필요한 경우도 그냥 Map에서 빼버리면 됨.
    • 한 눈에 어떤 데이터가 내려가는지는 컨트롤러에서 map에 할당하는 로직을 보면 확인이 되기는 함.
      • JS에서 객체 리터럴로 json 만들어서 내려주는 느낌으로...
      • 근데 컨트롤러 로직이 좀 지저분해지기는 한다.
      • 재사용하는 부분이 있으면 그 것 대로 문제가 되기도 하고.
        각 컨트롤러 마다 Map에 넣기도 그렇고 이 로직을 다른곳으로 빼기도 좀 그렇고.
    • 단점은 annotation을 사용할 수 없다는 점. 그래서 swagger를 못쓴다.
  3. 응답용 데이터 클래스를 아예 별도로 만드는 방법.
    • `` OverseasCardAdminResponse`` 라던가.
    • 제일 추천하는 방법

 

결론) Model은 어떻게 만드는게 좋나?

결론은, 정석대로 간다면 비즈니스 로직에서 쓰는 Domain Model 클래스와, 요청/응답에서 사용한 Request/Response Data 클래스를 분리하는 것이 좋다. (DDD)

그래서 Map보다는, Request/Response Data Class를 사용하게 되는 것이지.

 

  • 비즈니스 레이어에서 사용하는 모델 클래스는 단순 필드 뿐만 아니라 로직도 가지고 있게 된다는 점도 그렇고.
    • 책임을 생각하면 요게 맞다. 코드가 더 깔끔해 지는 경우가 다수 있음.
  • 클래스를 또 만들어? 라는 생각이 들 수 있지만, 사실 그게 제대로 하는 것.

 

 

 

같은 맥락에서, 각 layer 별로 Data Class를 따로 만드는 것이 정석이긴 하다.

  • 비즈니스 레이어에서 사용하는 Model은, 비즈니스 레이어에서 필요한 정보를 반영해서 만들어야 한다.
    • (Domain-Driven approach)
  • 즉, 서버가 가진 자원 자체를 나타내야 함.
  • front-end, DB 등 다른 layer에서 요구하는 데이터 모음 그대로를 Model로 만들면 안됨.
  • 사실 제일 좋은건, 각 layer 별로 Data Class를 다 따로 사용하는 것이다.

 

예를 들면?

front가 요구하는 데이터로 비즈니스 레이어의 Model을 만들어버리면, layer를 나눠 놓은 의미가 없어진다.

Presentation layer, Business layer, Persistence layer 를 나눠놓은 이유가 각자의 의존성을 떨어트리기 위함인데, 

이렇게 되면 서버가 가진 자원 자체가 아니라, 프론트가 어떤 데이터를 출력하고 있는지를 바탕으로 모델을 만들어버리게 된다.

  • 자원 자체를 나타내지 않으니 REST하지 않게 된다.
  • 뷰가 변경되어 해당 뷰에서 가져와야 되는 속성이 하나 늘어나면 그를 담을 수 있는 Model도 수정해야하고, 그걸 가져오기 위한 Repository도 수정해야 한다.

 

약간 더 구체적인 예?

뷰의 datatable에서 발주번호, 제품이름, 입고날짜를 요구한다고 이거 세개를 조합한 하나의 모델을 만들어버리면, 뷰가 변경되어 업체이름이라는 속성을 추가해야 할 때,

1. 모델에도 추가해야되고, DAO도 수정해야되고 아주 복잡해지며

2. 모델이 딱 그 뷰에만 연결되어 있기 때문에 재사용성이 떨어지며 재사용성이 떨어지니까 비슷한 모델을 또 만드는 결과로 이어질 수 있다.

 

결론은?

Model은 비즈니스 레이어에서 필요한 데이터 타입으로 생각하고

다른 layer에서 커버가 안된다면, 해당 layer에서 사용할 Data Class를 만들거나 Map을 사용하는게 맞다.

  1. 엄격하게 한다면 Persistence layer Data Class와 Business Model은 분리하는 것이 좋지만 보통은 이 Model 하나로 가는 경우가 많다.
    • DB 설계할 때 비즈니스 로직을 반영하기 때문에, 굳이 나누지 않아도 문제 없는 경우가 많아서.
  2. 보통은 (DTO, VO)는 테이블 대로 사상하거나 약간 더 복잡한 수준으로, DB layer의 추한 부분을 추상화 한다는 생각으로 만들게 되는 듯.
    • DB 가져와야 할 데이터를 1차원적으로 쫙 펼쳐서 모델로 만들지 말고, 발주 안에 List<발주업체>가 있고, 발주업체 안에 List<발주물품>이 있는, 구조화된 형식으로 모델로 사상하게 된다

쿼리가 너무 복잡하게 되고 재사용성이 떨어지면 Persistence layer DTO와 Model을 분리하는 것을 고려해본다.