프론트엔드 아키텍처

Pakxe·2023년 8월 11일
0

React

목록 보기
5/8
post-thumbnail
post-custom-banner

현재의 방향성

View와 비즈니스 로직을 완전히 분리하여 생각한다.

이때 비즈니스 로직은 1) 데이터를 관장하는 Model과 2) 사용자의 행동을 데이터 변화로 매핑하는 intent 영역으로 분리한다.

Model은 다시 1) 변화를 감지하고 변경사항을 전파하는 영역과 2) 데이터를 변화하는 로직을 구분해서 작성한다.

이를 통해 View에서는 Model로부터 데이터를 조회하는 Query와 데이터를 변화시키는 Commend를 언제든 조립해서 사용할 수 있다.
또한 View는 비즈니스 로직에 의존적이지만, View끼리는 느슨하게 결합하여 UI 요구사항 변화에 긴밀하게 대응할 수 있게 된다.(Props를 안쓰니까 그런 것 같음)

기존과 현재

프롭스드릴링을 유발하며 경직된 구조를 만들어내는 중간 계층의 컴포넌트들은 사실 독립적이지도 않고, 재사용도 되지 않는 컴포넌트인 경우가 많다. 이러한 컴포넌트들은 대개 배즈니스 로직을 담당하기 때문에 거대한 컴포넌트가 되기 마련이다.

기존에는 '컴포넌트는 독립성을 유지하고 의존성을 최소화해야한다'는 원칙에 따라 최대한 props를 통해서 컴포넌트 계층 구조를 유지하려고 했다. 하지만 이 방식이 되려 경직된 구조를 만들어냈기에 어차피 재사용하지 못할 컴포넌트라면 비즈니스 모듈에 의존적이더라도 언제든 교체 가능한 방식으로 만들어내는 것이 변화에 대응하기 좋다.

(비즈니스 모듈에 의존적이라는건 모델을 말하는건가?)

비즈니스 로직?: 예를들어 회원가입을 하려 한다면 아이디 중복 찾기, 본인 인증, 비밀번호 재확인 등의 일련의 과정들을 비즈니스 로직이라고 부른다. 개발자는 이 '비즈니스 로직'을 잘 구상해야 코드 관리도 쉬워지고, 생산성과 품질을 향상시킬 수 있다.

Model

모델은 뷰와 컨트롤러에 대해 어떤 정보도 알고 있으면 안된다.
변동사항을 통지할 방법을 구현해야한다(이게 리코일일듯)

View

모델, 컨트롤러를 알 필요가 없다. 컨트롤러가 하라는 대로 요청하면 그만인 것 같음. 그리고 요청 사항(액션)을 통지할 방법이 필요하다(이게 리코일?)

Controller

둘을 잇는 다리 역할. 사용자가 데이터를 수정하거나, 클릭하는 등의 이벤트 발생시 이 이벤트를 처리한다.
컨트롤러는 뷰와 모델을 알고있어야 한다.

기존 코드

// view에서 직접 여러가지 값들의 변화를 기술한다.
const onClick = () => {
  setVisible(!visible)
  setCount(count+1)
  setTodos([...todos, {title: '밥먹기'}])
}

이런 방식은 우리가 아는 실행 순서와 일치하므로 작성하기 쉽다. 하지만 디버깅할때 어려움이 생긴다. 디버깅은 결과를 보고 원인을 찾아내는 역순의 과정... 이기 때문이다. 데이터가 변화하는 view코드에 흩어져있으면 어떤식으로 데이터가 변화하는지 추적이 어렵다. 또한 view에서 직접 데이터를 수정하도록 작성하면 view와 모델간 의존성뿐만 아니라 모델과 view간의 의존성이 생긴다(???이건뭐지)

따라서 view는 의도만을 전달하고 의도에 맞는 데이터 변환은 모델에서만 처리할 수 있도록 해야한다. 데이터 변화 코드를 model 모듈에 모르게 되면 응집도가 높아지고 추후 디버깅에 용이하며, 특히 view가 비즈니스 로직으로부터 느슨한 결합을 할 수 있다.

이를 이해하기 위한 의사 코드

// action
export const _INCREASE = action('_INCREASE');
export const _DECREASE = action('_DECREASE');
export const _RESET = action('_RESET');

// model
export const store = createStore({count: 0}, state => {
  on(_INCREASE, (value) => state.count += value))
  on(_DECREASE, (value) => state.count -= value))
  on(_RESET, (value) => state.count = 0))
})

// component
export const App = (props) => {
  
  // query
  const count = SELECT(store.count);
  
  // compoted value
  const doubledCount = count * 2;
  
  // intent
  const onClick = () => dispatch(_INCREASE(+1));
  
  // view
  return <button onClick={onClick}>{count}</button>
}

이런 방식으로 작성하게 되면 화면을 구성할 때 컴포넌트 간의 종속성이나 비즈니스 모델의 구현과 관계 없이 유연하게 화면을 만들 수 있게 된다.

// component 1
const Component1 = (props) => {
  const count = SELECT(store.count);
  return <div>{count}</div>
}

// component2 
const Component2 = (props) => {
  const onClick = () => dispatch(_INCREASE(+1))
  
  return <>
    <Component1/>
    <button onClick={onClick}>1 증가</button>
  	</>
}

참고

https://yozm.wishket.com/magazine/detail/1663/
https://do-rang.tistory.com/24

profile
내가 꿈을 이루면 나는 또 누군가의 꿈이 된다.
post-custom-banner

0개의 댓글