data-model 사용 이유가 있나요? 그렇다면 DTO와의 차이는 무엇인가요?
해당 모델을 사용한 목적과 도입한 결과에 대해서도 설명해주세요
지금 우리 회사 웹프론트의 아키텍쳐는 mvvm 패턴으로 되어있다.
그래서 domain > entity를 두고 서버의 entity 클래스와 동일한 interface(형태 데이터)를 view에 뿌려주고 있었다.
데이터의 가공이 필요한 경우 viewmodel과 view에서 그때그때 만들어 사용하고 있었다.
서비스는 커지고, 한 api를 여러군데서 콜하다 보면 당연하게 UI 렌데링에 필요한 데이터는 다를것이고,
(서버에서 전달 받은 데이터는 UI를 렝더링하기에 최적의 데이터가 아니였기 때문)
그때마다 api를 새로 만들 수는 없는일.
(유지보수의 어려움도 당연함)
서비스가 커질 수록 서버는 도메인 단위의 데이터관리가 필요했기에, 프론트는 이에 따라 UI에 필요한 데이터 단위를 만들 수 밖에 !
그래서 우리는 data-model을 사용하기로했다.
entity는 실제 DB에 있는 데이터(형태) 그대로 냅두고,
View에 따라 자주 변경될 데이터는 class를 이용한 model을 만들어 사용하기로 함!
(DB Layer와 View Layer 사이의 역할을 분리에도 좋았음)
굵직한 서비스 내 컴퍼넌트 별로 data-model을 폴더링하고 > 그 안에 컴퍼넌트를 하나 생성 > view 마다 필요한 data-model class를 생성했다.
export class PaginationInfo<T> {
offset: number;
size: number;
total: number;
totalPage: number;
sort?: T;
constructor(pagination: Entity.Pagination) {
//클래스의 인스턴스 객체를 생성, 초기화하는 역할
this.total = 0;
this.offset = 0;
this.size = pagination.size; //Entity데이터를 받아 해당 객체의 변수에 값 할당
this.sort = pagination.sort;
this.totalPage = 0;
}
next = () => {
if (this.offset + 1 < this.totalPage) {
this.offset = this.offset + 1;
}
};
prev = () => {
if (this.offset > 0) {
this.offset = this.offset - 1;
}
};
setSort = (sort: T) => {
this.sort = sort;
};
//class외부에서 해당 데이터를 업데이드 하도록 만든 함수
setTotal = (total: number) => {
this.total = total;
this.totalPage = Math.ceil(this.total / this.size);
};
//private 하게 class 내부에서만 사용할 수 있는 함수도 생성함
private setOffset = (offset: number) => {
this.offset = offset;
};
}
//필요시 타입을 선언하여 사용하기도 하고
export type FilterType = "A" | "B" | "C";
//interface만 사용하기도함
export interface Media {
s: string | null;
m: string | null;
l: string | null;
xl: string | null;
origin: string;
}
mvvm패턴을 사용하는 우리는 최종적으로 viewmodel을 통해 view에 필요한 api를 콜해 데이터를 받는데
이때, entity -> data-model로 바꾸는 작업을 한다.
const viewData = new DataModel.PaginationInfo(entity)
//new 생성자 함수를 통해 class를 생성한다
이렇게 생성된 데이터를 UI 렌더시 사용한다.
지금 회사의 DTO는 domain 계층에서 사용하고있다.
view 레이어에서(UI용 DTO) data-model class를 생성해 사용중이였고, api가 리스펀스 값이 entity에 정의 되어있지않은 값으로 보내줄 때 데이터 변환점이 하나더 필요했다. 그래서 domain 계층에 DTO를 생성하게 되었다.
결국 (레이어) 계층간에 데이터교환을 위해 각 계층별로의 변환될 객체가 필요했고 이를 계층별 DTO로 생성하면 되는것!
UI에 필요한 데이터만 걸러서 사용한다.
-> 비즈니스 로직에 담긴 민감한 정보들이 외부에 노출될 일 없음
-> entity데이터는 확장성을 가지고 있어 UI에 불필요한 다량의 데이터 존재
재사용성이 높다.
계층간의 역할분리가 명확하다.
entity의 변수명과 DTO의 변수명이 분리 되어있기 때문에, entity의 변수명이 변경되어도 UI에 에러날 케이스가 적다.
view의 로직이 분리되어있어 로직 수정, 테스트에 용이했다.
데이터 흐름이 역전 될 때(코어로 데이터를 다시 올려줄때), DTO -> entity로 변경해주는 과정이 번거롭게 느껴졌다.
class로 DTO를 생성해 class 내부 함수로 데이터를 업데이트 하다보니 리액트가 변경된 값을 제대로 인지 못하는 상황 발생
-> 해결 : 데이터 상태관리는 hooks(useState로 사용)하고 있어서 데이터 변경 후 setState를 할 때, 깊은복사(Deep Copy)를 통해 업데이트 해줌
UI 필요한 로직을 전부 data-model > class 단위 > private 함수로 만들어 두었다.
//데이터 타입에 따라 노출될 텍스트 구하기
private revert = () => {
switch (this.countryCodeState) {
case CountryCode.ALL:
return "all";
case CountryCode.KOREA:
return "korea";
case CountryCode.CHINA:
return "china";
case CountryCode.ETC:
return "etc";
}
};
해당 함수는 ui상 보여질 텍스트를 구하는 것이고, 여러군데에서 같은 로직이 필요하다.
그렇다면, class 단위의 함수가 아닌 해당 data-model 컴퍼넌트 혹은, 유틸 함수로 만들어서 재사용하는 것이 더 좋았겠다!
MVVM패턴의 DTO와 ENTITY를 너무 시원하게 그려두셔서 가져와봤슴댜 MVVM + Clean Architecture