리팩토링 선정 이유

우리가 리액트/리액트 네이티브 프로젝트의 스타트 킷에서는 아래와 같은 구조로 프로젝트를 만들어준다.

└─ src
   ├─ components
   ├─ constants
   ├─ containers
   ├─ contexts
   ├─ hooks
   ├─ utils
   └─ ...

와 같은 프로젝트의 구조이다. 현재 리액트 기반의 프로젝트에서는 de facto라고 생각한다. 하지만 나는 이 프로젝트의 구조를 그렇게 좋아하지는 않았다. 컴포넌트 하나를 고치더라도 여러 역할을 가지는 계층들을 오고가며 수정해야 할 필요성이 있었고, 프로젝트가 복잡해지면 복잡해질수록 점점 유지보수가 어려워지는 느낌을 받았다. 이번 포에이 리팩토링을 하면서도 특히 심하게 느꼈던 부분이었다.

물론 이 프로젝트의 구조 자체가 틀렸다는 말을 하는 것이 아니다. 과거의 개발자들 또한 프로젝트 구조에서 보다 좋은 구조를 만들어내기 위해 다양한 관심사를 기준으로 여러 방법을 시도했다. HTML + CSS + JS(+jQeury) 정도로만 이루어졌던 과거 프로젝트에서는 역할을 중심으로 수평적으로 계층적인 관심사를 나누려고 시도했었고, 프로젝트가 복잡해지고 웹이 발달함에 따라 기존의 수평적 계층 구조를 수직으로 관통하는 컴포넌트라는 개념을 만들어 다시금 새로운 분류 방식을 제안하기도 했다. 그리고 다시 이 컴포넌트가 점점 복잡해짐에 따라 다시금 계층적 관심사로 돌아가기도 하고 하는 등 많은 역사를 거쳐 현재의 프로젝트 스타트킷 구조가 나오게 된 것이다.

위와 같은 프로젝트 구조는 코드의 역할을 기준으로 나누어져있다. 기존의 컴포넌트 중심으로 나누어진 구조에서 점점 컴포넌트가 Props Drilling문제와 컴포넌트 파일들의 비대화로 인해 기존의 구조에서 다시금 계층적인 구조로 관심사를 돌려 역할을 명확히 할 수 있도록 분류한 구조이다.  하지만 이러한 역할을 중심으로 다시금 계층적 구조로 분리했을 때 과거에서도 있었던 문제가 나타나게 되고, 나 또한 이를 느꼈다. 포에이의 구조에서 가장 크게 느낀 점은

  • 컴포넌트의 비대화
    • 비즈니스 로직, UI, 데이터 관리 등 너무 많은 로직을 하나의 컴포넌트가 담고 있다
    • Screen을 따로 나누지 않아 구조 파악이 어렵다
  • 결합도와 함께 높아진 유지보수 난이도
    • props drilling을 통해 컴포넌트의 결합도는 높아졌지만 결합되어 있는 컴포넌트가 하나의 폴더 안에 산재되어 있어 유지보수 하기가 매우 어려웠다 였다.

탭에는 병원(위치정보와 리뷰 등 제공), 약(약 정보와 리뷰 등 제공), 오늘(커뮤니티), 마이페이지가 있다. 위에서도 각 탭은 하나의 기능만 가지고 있는 것이 아닌, 리뷰를 달거나 위치 정보를 제공하고, 리뷰를 직접 쓰는 등 다양한 기능들이 제공된다. 이러한 기능들이 각 탭마다 있다고 생각하면 생각보다 프로젝트의 규모는 크다고 생각한다. 하지만 큰 프로젝트의 규모에 비해 프로젝트 구조가 점점 복잡해지다보니 유지보수의 효율성이 떨어지는 지점까지 왔다고 생각한다. 이에 이번 기회에 리팩토링을 하면서 나는 도메인 별로 다시금 컴포넌트와 훅, api등을 다시금 기능에 따라 분리해보려고 한다.

기능별로 분리하기

최근에(?) 핫해진 프로젝트 구조 패턴중에 FSD(Feature-Sliced Design) 아키텍처라는 것이 있다. 위 사진처럼 프로젝트의 규모가 커지면서 보다 효율적으로 프로젝트를 관리하기 위해 Feature-Sliced한, 즉 기능별로 자른 프로젝트 구조이다. 하나의 기능 단위로 필요한 컴포넌트, api, 데이터 구조 등을 같은 파일에 놓고 관리하는 방식은 중간 이상의 규모를 가진 프로젝트에서 유지보수성과 확장성과 같은 면이 크게 상승한다. 기능별로 분리하는 만큼 디렉토리가 하나 생기면 기능이 추가되고, 디렉토리 하나를 삭제하면 기능이 하나 없어지는 느낌의 구조이기 때문에 내가 현재 하고 있는 프로젝트에서 다양한 기능들을 효과적으로 관리하기에 좋은 구조라고 생각했다.

사실 나는 FSD 광팬이긴 하다(?). 예전부터 역할별로 프로젝트를 나누고 개발을 수차례 했었고 이때마다 커지는 프로젝트의 규모때문에 유지보수에 난항을 겪었던 적이 꽤 있었다. 실력이 안좋아서일지도.. 그래서 어떤 구조가 좋을까 계속 고민하던 시기가 있었는데, 운좋게 부스트캠프에서 해당 키워드를 듣고 여기에 푹 빠져서 프로젝트 초반부터 FSD로 했다가 벌써부터 FSD 아키텍처로 프로젝트를 구성하는건 시기상조라는 지적을 받기도 했었다.

하지만! 이번에는 정말 해야 될 필요성을 느껴서 한번 기능별로 분리해보는 작업을 해보려고 한다. 하지만 그렇다고 FSD 패턴 그대로 사용을 할 것은 아니다. 현재 프로젝트 구조와 React-native의 특성을 조금 더 살린 채로 비슷한 느낌을 가져갈 수 있게끔 할 것이다.

문제 1 : 서로 다른 도메인에서 같은 API를 사용한다.

└─ domains
      │  
      ├─ HospitalList
      │     ├─ components
      │     ├─ api
	  │		│   └─ bookmark.api.ts
      │     ├─ hooks
      │     └─ ...
      │  
      └─ HospitalDetail
		    ├─ api
			    └─ bookmark.api.ts

프로젝트 구조를 갈아엎던 중 한가지 고민이 생겼다. HospitalList에서 병원들의 목록을 볼 수 있고, 해당 목록 옆에는 북마크(즐겨찾기) 기능이 있다. 여기까지는 HospitalList에 그냥 bookmark.api.ts 파일을 놓으면 된다.

하지만 bookmark.api.ts 파일은 HospitalDetail 도메인에서도 북마크 기능이 있어 api가 서로 다른 도메인에서 사용되는 현상이 발생하게 된다. 이런 경우에 똑같은 코드인데 각각의 api 파일을 두게 된다면, 응집도와 가독성을 위해 효율을 버리는 꼴이 되어버린다고 생각한다. 그래서 계속 어디에 위치하는게 좋을까? common이라는 도메인을 둬야하나? 그건 또 애매한 도메인같은데..라는 생각을 하기도 했고, 상위 도메인에서 하위 도메인으로 중첩되는 구조로 바꾸기에는 나중을 생각했을 때 프로젝트 구조가 복잡해서 바꾼 구조를 다시 복잡하게 만드는 꼴이기 때문에 아닌 것 같다고 생각하기도 했다.

여기서 내가 내린 결론은 두 도메인을 아우르는 공통 도메인을 만들자 였다. HospitalListHospitalDetail을 아우르는 Hosptial 도메인을 같은 depth에 두게 된다면 해당 도메인에서 여러 하위 도메인을 아우르는 컴포넌트나 api등 또한 유용하게 사용할 수 있고, 공통 도메인에서 다른 도메인으로 접근하는 구조는 강하게 결합된 구조가 아닌 단방향으로 의존성이 흐르기 때문에 좋을 것 같다고 생각했다.

TOSS의 Frontend Fundamental 에서도 나와 비슷한 고민을 하셨던 분이 계셨는데, 이 분의 정리한 내용이 내가 생각했던 부분과 같아 많이 공감하게 되었다.

if (도메인_간_강한_연관성 && 공유_코드_증가_예상) {
  return '새로운 도메인 생성';
} else if (단순_기능_공유 && 제한적_사용) {
  return '공통 폴더 하위 도메인';
}

맨 아래서 정리하신게 위와 같은데, 나 또한 이러한 생각에 동의하는 바이다. 단순 기능 공유나 얼마 파일이 되지 않는다면 공통 폴더에 하위 도메인을 두고 관리하는 것이 보다 가독성 측면과 의존성을 관리하는데 더 좋은 방법이라고 생각하지만, 도메인간 강한 연관성을 가지는 코드들이 많은 경우에는 새로운 도메인 생성을 통해 한쪽으로 의존성을 지속적으로 관리하는 것도 좋은 관리법이다.

나의 경우 HospitalListHospitalDetail은 결국 Hospital로 추상화되고, 해당 추상화된 도메인에서 여러 하위 도메인들이 공유하는 api들과 컴포넌트들이 있다. 그렇기에 나는 두 도메인을 아우르는 새로운 도메인 생성이라는 선택지를 고르게 된 것이다.