nested vs normalize vs graphql

반응형

nested: 다음과 같이 Sender, Receiver 부분이 IUser 로 nested 된 형태로 필요한 부분까지 전체 데이터를 front 측 모델로 다룬다.

export interface IUser {
  id: number;
  nickname: string;
  email: string;
  updatedAt: Date;
  createdAt: Date;
}

export interface IDM {
  id: number;
  SenderId: number;
  Sender: IUser;
  ReceiverId: number;
  Receiver: IUser;
  content: string;
  updatedAt: Date;
  createdAt: Date;
}

normalize: 다음과 같이 Sender, Receiver 가 string 으로 id 값만 가지고 IUser 는 따로 state 를 만들어 정규화 하는 방식으로 함.

export interface IUser {
  id: string;
  nickname: string;
  Images: string[];
  updatedAt: Date;
  createdAt: Date;
}

export interface IDM {
  id: string;
  type: string;
  content: string;
  SenderId: string;
  Sender: string;
  ReceiverId: string;
  Receiver: string;
  Image: string;
  updatedAt: Date;
  createdAt: Date;
}

 

이 둘의 방식에 있어 normalize 방식을 사용하여야 한다. nested 방식으로 하면 서버로부터 데이터를 가져와서 그대로 컴포넌트에 뿌려주는 정도에서는 효율적일 수 있지만 이것이 state 가 되면 불변성을 고려해야하고 데이터의 변경에 따른 UI 갱신도 고려해야 하고 전역 state  일 수도 있는 부분에 대한 고려도 하여야 하기 때문에 id 들의 집합 객체와 해당 id 를 가지는 데이터들의 배열의 형태로 normalize 된 마치 관계형 DB 의 테이블 정규화와 유사한 형태로 사용해야 이러한 문제들을 해결할 수 있다.

 

 

https://github.com/dmk3141618/Dev-Docs/blob/master/Javascript/Redux%20State%20정규화.md

그냥 단순히 nested 된 형태 그대로 사용할 경우 다음과 같은 문제에 부딪칠 수 있다.

When a piece of data is duplicated in several places, it becomes harder to make sure that it is updated appropriately.
(데이터가 제대로 업데이트 되었는지 확인하기 어렵다.)
Nested data means that the corresponding reducer logic has to be more nested and therefore more complex. In particular, trying to update a deeply nested field can become very ugly very fast.
(Nested 데이터가 늘어나고 깊이 내장되어 있는 데이터일수록 업데이트하기 어려워진다.)
Since immutable data updates require all ancestors in the state tree to be copied and updated as well, and new object references will cause connected UI components to re-render, an update to a deeply nested data object could force totally unrelated UI components to re-render even if the data they're displaying hasn't actually changed.
(불변 데이터를 업데이트 할 때 깊이 내장된 데이터일수록 상위 데이터들까지 업데이트 되어야하고 이는 관련되지 않은 UI 컴포넌트까지 리렌더링될 수 있다.)

 

normalize 방식을 사용할 경우 서버측에서 내려줄 때 nested 된 형태로 내려주며 프론트 측에서 이를 받아서 정규화 해준다.

 

normalize 방식을 사용할 경우 서버와 프론트가 완전히 독립되어 개발하기가 거의 불가능할 수 있다. 사전 조율을 잘 할 경우 가능할 수도 있다. 서버에서 프론트와 잘 조율을 거쳐서 내려준 nested 된 데이터를 프론트 측에서는 거의 매번 정규화 작업을 해주어야 한다. 이러한 단점이 존재한다.

 

graphql 은 실제 적용 경험이 부족하여 관련한 글은 추후 업데이트 해야할 듯 하다. normalize 의 대안이 될 수 있을까? nested 는 분명히 잘못된 방식이라 현재의 final solution 은 normalize 이지만 graphql 이 이를 더 범용적이고 명료하게 해주는 최종 해법이 될 수 있는지에 대한 것은 graphql 에 대한 실제 적용 경험이 부족하여 언급을 하지 못하겠다. 확실히 graphql 하나만으로도 학습량이 높아지는 것은 분명하다. normalize 를 하기 위해서 서버와 프론트가 DB 구조에 맞게 유기적으로 맞추어서 개발을 해야만 하고 서버 측에서도 위의 코드들과 같이 nested 된 부분에 대해 잘 구성하여 내려주어야 하며 프론트 측에서도 이를 염두에 두고 프론트 쪽 db 타입을 잘 정의하고 데이터를 받았을 때 또한 normalize 작업을 해주어야 하므로 해법은 될 수 있지만 아직까지 어려울 수 있는 부분들이 많이 있다. 서버측과 프론트가 완전한 분업이 이루어지기 힘든 부분 그리고 데이터를 받을 때 마다 정규화 작업을 고려해주어야 하는 부분 등이 개발을 어렵고 복잡하게 만들 수 있다. graphql 의 학습량이 좀 높더라도 서버와 프론트의 완전한 분리 개발 및 정규화 작업을 해야하는 과정을 해결하면서도 개발을 문제없이 할 수 있게 해준다면 적용을 하는 것도 좋을 것 같아보인다. redux 의 store state 관련한 부분과 graphql 의 영역은 다른 것일까? 지식과 경험이 좀 부족하여 이 부분에 대한 답은 차후에 업데이트 해야겠다.

 

 

 

항상 궁금한 것이 잘 돌아가는 상용 서비스 같은거 예를 들면 당근마켓 같은 것들은 프론트와 서버가 어떻게 협업을 하길래 그렇게 큰 서비스가 의도대로 잘 문제 없이 만들어져 가는지 궁금하다.

 

프론트에도 뷰모델이랄까.. 프론트쪽의 데이터 저장 구조가 있는데 관계형 데이터베이스의 조인된 중첩된 객체 형태의 데이터는 어떻게 주고받을까? - 타입 같은 것을 공통 모듈로 만들어 놓기도 하고 아니면 그래프 큐엘을 쓸 수도 있고 그래프 큐엘은 타입이 정해져 있을 수 밖에 없고 프로젝트 규모에 따라 다를까? 개발의 속도나 편의성, 안정성, 유지보수 등을 고려했을 때 어떻게 해결하는 것이 좋은 것일까?

 

gql 은 좀 복잡한 엔드포인트가 필요할 때 좋은 것 같은데 크지 않다면 개발 효율로는 rest 닮은꼴이 나을 수 있다고 한다.

API 가 복잡하고 다양한 쿼리를 다뤄야 하면 그래프큐엘이 좋은데 프론트 개발자가 일이 많아진다고 한다. 

상황에 따라 다르다고 한다... 내부가 그래프큐엘이라도 외부용은 rest 스러운 http api 를 따로 만들기도 한다는데.. 이러면 그냥 rest 스러운게 편할거 같구... https://nexusjs.org 넥서스 좋아요... 타이핑이 안되는 api 는 싫다고 한다.

관계형 디비에서 nested 된 object 같은 거 타이핑은 swagger 의 openapi 3 스펙으로 만들어 쓴다고 한다. 스웨거는 그냥 UI 이름이고 그냥 open api.. https://swagger.io/specification/ 

 

백엔드 엔드포인트를 sdk 식으로 맞추어 주어도 좋다고 한다. http 요청 프론트에서 보내면 오타나고 그러니... js sdk 로 만들어서 주는것.

sdk 써서 보내게끔 하면 타이핑도 미리 다 넣어둘 수 있고 좋다고 한다. proto 스펙으로 정의하고 js sdk 만들어 쓰면 좋다고 한다. 버전 관리도 되고... 그러면 프론트는 그거 그냥 임포트 해서 쓰면 된다. 프로바이더로 제공해서 그냥 부른다. 인증도 sdk 로 해결하고... 워낙 복잡해서 정적 타이핑 꼭 되어야 해서... 제가 봤을 땐 sdk 가 제일 나은 방법인 것 같습니다... 그러면 그런 sdk 는 npm 에 private 으로 올려서 쓰는건가요?... 네 프라이빗으로 해서 쓰고 있어요... 저희는 깃헙 패키지로... 저희는 모노레포라서 다 포함되어 있습니다. 프론트 미들웨어 백엔드... 오 모노로 해서 경로로 패키지 특정 이런식으로 해도 되겠군요...

 

https://docs.microsoft.com/ko-kr/aspnet/core/grpc/comparison?view=aspnetcore-5.0 

grpc 쓰나봐요? grpc 를 안쓰는 구간도 proto 로 정의해요.... 그게 타이핑 하기 쉬워요... 타이핑 하기 쉬우나 낯설져 버퍼라는 개념이...

 원래 오리지널 버퍼스트럭처가 정적이에요... 직렬화거든요... 아뇨 프론트에서 TS 코드 작성할 때요... 저희는 직렬화 하는 구간도 있고 안하는 구간도 있어요... 오타나면 빌드 터지는게 최고 ㅋㅋ... 아니요 grpc 의 초기 개념이 직렬화에 네이티브의 스트럭처 위 개념이에요...  grpc 에서 직렬화를 합니다... 그거야 알죠. 상식인데... 그거랑 프론트 정적 타이핑이랑 상관없단 말이었어요... 있어요!!...  grpc 는 browser 구현체도 없어서 상관없어요... grpc 브라우저 구현체가 전재합니다.. 크롬만되져.. 다른건 안되는게 함정.. 그건 웹어셈이잖아요 ㅎ.. 제대로 쓸 수 있는 상태가 아니던디... 아뇨 웹어셈 안써도 됩니다... grpc-web 은 프록시라서 구현체가 아니고 다른게 있어요?... 크롬 특정버전 기준으로 grpc 가 브라우저로 호출이 가능해요... 그럼 일렉트론 만들땐 ui 에서 grpc 바로 부를 순 있겠네요... grpc-web 맞네요...  에잉 그거 못써요... 저희가 grpc 되게 오래 쓰고 빡세게 써서 web에서 되면 참 좋겠는데... 크롬미움만 가능하죠 어쩔수없는데... 그게 안되서 직접 socket.io 로 만든 grpc 프록시 사용합니다... websocket 에도 연결할 수도 있고... mq 로도 가능하죠... grpc-web 그지 같아서 이걸 오픈소스로 공개하긴 해야하는데 grpc-web 이 go proxy 라서 직접 socket.io 써서 node proxy 로 만든거 있거든요... 공개할 시간이 없네요... mq 를 이용해 grpc 구성도 가능하십니다... grpc 가 aws 에서 최근에 지원되서... mq 도 go 군요. star 를 보니 아직 얼리한 느낌. 마지막 업데이트가 5년전. mq 되게 오래됐어요. 얼리가 아니라 죽은 프로젝트네요. 다른 큐도 많이 쓰죠. mq 는 종류가 몇개 안되죠. 저희 grpc 로 한번에 70g 짜리도 전송하고 그래서 완성도가 중요해서리... grpc 가 완성도라... grpc 자체는 문제 없는데 서버 구현체나 클라 구현체가 완성도가 낮아서 고생하는 경우가 많습니다. grpc 가 로드밸런서 지원 안됐었는데... 올해 지원됐어요 aws 등.. 저도 작년까지 grpc 썼던.. 엔진엑스 플러스나 haproxy 를 써야했으니..  로드밸런서 저희는 쓰고 있었어요...

sdk 단점은 sdk 만들어야 한다는건데... 자동화된 툴이 노드쪽에는 많이 없어서... 

 

https://redux.js.org/usage/structuring-reducers/normalizing-state-shape
https://redux-toolkit.js.org/usage/usage-guide#managing-normalized-data
import { normalize, schema } from 'normalizr' 이거 사용하시는가요? 이거하면 소스코드가 좀 지저분해져서요.

서버에서 준 sdk 에서 리턴되는 데이터를 잘 파싱해서 normalize, schema 에 잘 맞게 파싱하는 메소드도 하나 만들고 하는 과정으로 만들면 좀 깨끗해지려는지 그런데 제가 봤을 땐 리덕스 스토어에서 정규화가 필수적일 거 같은데... 혹시 정규화 안하고 더 간단한 다른 방법도 있을까요? 리덕스 스토어 라던가 리액트 컴포넌트의 상태는 nested 된 object 형태를 지양해야 하고 이로 인해서 과거에 저도 많은 문제를 겪었었구요. 그래서 정규화 했더니 문제는 없는데... 저 정규화하는데 코드가 좀 더 들어가서 normalize 하고 schema 를 써서 정규화 할 때 서버 응답받은 데이터를 파싱할 때 긴 코드가 또 들어가서 단순 api 응답받고 set 해주는 거보다 중간에 정규화 과정을 또 거치다 보니 귀찮고 코드도 좀 더 더러운거 같구해서요. 서버는 확실히 정규화나 비정규화 하지만 프론트도 리덕스 스토어가 https://github.com/im-d-team/Dev-Docs/blob/master/Javascript/Redux%20State%20정규화.md 여기에 언급된 문제로 인해서 정규화가 필요한 걸로 판단했거든요. 약간 서버의 정규화와 프론트의 정규화는 의미가 완전히 동일하지는 않아서... 프론트 측은 프론트 측에서 해주어야 할 일 같구 그러면 또 서버와 의사소통이 필요할 수도... 원하는 건 서버는 서버대로 막 만들고 / 프론트는 프론트대로 막 만들면서도 단순하고 쉬운 그런게 있을까해서요. 상용 서비스들 보면 문제없이 정말 잘 돌아가는거 보면 신기해요..

 

참고로 정규화 안 하는 곳도 많습니다.

 

아, 저 문제들만 해결된다면 정규화 안하는게 좋은거 같은데.. 안하고 단순화 시키더라도 방법론이 있을까 해서 찾고 있어요. 그런데 리덕스 툴킷 공식 문서에도 언급되는거 보면 하긴 해야 하는거 같기도 하고.... https://redux-toolkit.js.org/usage/usage-guide#normalizing-with-normalizr 요기서 언급을 하는걸 보면... 단순히 이렇게 UserEntity 하나만 있으면 안 복잡한데... 이게 관계형 디비에서 시퀄라이즈 같은걸로 조인해서 nested 된 형태로 온 데이터가 몇개만 들어 있어도 파싱하는데 코드가 좀 길어져서요. 그게 schema 여러개 쓰는걸로는 해결이 안 돼요?? 아 해결이 되는데 추가 코드가 좀 들어가니 api 받는 곳 마다 그에 맞는 파싱하는 코드가 또 들어가는게 ... 그리고 서버측에서도 데이터를 잘 명칭까지 딱 맞게 보내주어야 하는 점도 있구요. 보통 원하는 건 api 호출 하면 응답 받으면 단순히 해당 데이터 가지고 set 하면 되도록 하는게 좋은데... 서버의 응답의 구조의 명칭까지 프론트에서 바꿔가면서 또 그것까지 파싱하는 작업 들어가는건 더 아닌거 같구.. 그러다 보면 서버랑 프론트랑 일일이 대화를 또 해야하고 규모가 커지면 감당 안 될 게 뻔하구...

 

 

 

 

 

 

반응형

댓글

Designed by JB FACTORY