다짐) 디자인 시스템 Git Submodule 관리 회고

2ast·2023년 8월 20일
1

왜 다짐의 디자인 시스템을 서브모듈로 분리했을까

깃에는 서브모듈이라는 기능이 있다. 간단하게 설명하자면 main project directory 내부에 다른 repo의 project를 추가해서 N개의 repo를 하나의 프로젝트에서 함께 관리하는 개념이다. 조금 더 쉬운 이해를 위해 폴더구조를 살펴보자

-dagym // main project
	-dagymDesignSystem //submodule
    	-src
          -assets
          -text
          -avatar
          ...
        -package.json
        ...
    -app.tsx
    -package.json
    -src
      -screens
      -components
      -hooks
      ...
    ...

dagym과 dagymDesignSystem은 깃헙에 각각 repo가 있는 별개의 프로젝트다. 하지만 폴더 구조를 보면 dagym 내부에 dagymDesignSystem이 포함되어 있는 것을 확인할 수 있다. 이처럼 N개의 repo를 하나의 프로젝트에서 관리할 수 있도록 하는 기능이 바로 submodule이다.
서브모듈을 사용하면 여러 프로젝트에서 사용하는 공통 코드를 손쉽게 공유할 수 있다. 마침 다짐의 디자인 시스템 구축 작업이 마무리되어 가는 시점에 다짐파트너에 디자인 시스템을 어떻게 적용할 수 있을까 고민하던 중이었기 때문에 굉장히 솔깃한 기능이 아닐 수 없었다. 실제로 팀내 웹 프로덕트는 이미 서브모듈을 성공적으로 적용하고 있었으므로 더욱 관심이 갈 수밖에 없었다. 망치를 든 사람은 모든게 못으로 보이는 법이다. 나는 더이상의 고민은 그만두고 즉시 디자인 시스템 폴더를 별도 repo로 분리했다.

디자인 시스템 서브모듈 회고

그리고 서브모듈 적용으로부터 4개월 정도의 시간이 흐른 지금, 나는 그 결정을 번복하려고 마음먹었다. 서브모듈은 굉장히 매력적인 기술인만큼 고려해야 할것도, 관리해야할 것도 많은 기술이다. 이번에는 디자인 시스템을 서브모듈로 관리하면서 어떤 점이 좋았고, 어떤 점이 불편했는지 공유해보려고 한다.

서브모듈의 장점

서브모듈의 가장 큰 장점은 여러개의 프로덕트에서 공통 모듈을 사용할 때 꽤나 쿨한 방식을 제공한다는 점이다. 서브모듈 없이 개별 프로덕트가 하나의 모듈을 공유하는 상황을 상정해보자. 공통 모듈을 클론해서 직접 각 프로젝트 폴더에 추가해줄 수도 있고, 프라이빗 라이브러리로 배포한 뒤 이를 install하여 사용할 수도 있다. 하지만 이러한 방법들은 모두 관리 측면에서 저마다의 어려움이 있다.
모듈을 클론해서 사용할 경우 어딘가에서 수정이 이루어진다고 해도 이 사실을 다른 프로덕트에서 알 방법이 없다. 적극적으로 공지해서 공유한다고 해도, 변경사항을 직접 수정해가며 수동으로 싱크를 맞출 수밖에 없다. 프라이빗 라이브러리 배포는 그나마 조금 더 낫다. 하지만 이 방법 역시 별개의 프로젝트를 열어 변경사항을 관리하는 절차가 꽤나 번거롭고, 매번 버저닝을 해서 배포하고 인스톨하는 절차도 마냥 편리하지만은 않다.
하지만 서브모듈은 현재 작업을 진행하는 프로젝트에서 그냥 레포 하나를 더 관리하면 될 뿐이다. 서브모듈에 변경사항이 생겼다면 서브모듈의 경로로 이동해서 커밋하고 푸시하면 된다. 브랜치를 나눠서 작업하고 머지하는 등 git 기능 사용도 모두 가능하기 때문에 개발단에서 훨씬 높은 유연성을 제공한다.

서브모듈의 단점

하지만 그만큼 단점도 명확하다. 가장 큰 단점은 관리 리소스의 증대다. 서브모듈 또한 일반적인 프로젝트처럼 깃헙의 플로우를 그대로 따른다는 장점은 동시에 단점이 되기도 한다. 서브모듈의 git status는 메인 프로젝트의 git status와 완전히 독립적이므로 N개의 branch 및 history 관리가 필요해지기 때문이다.
서브모듈을 사용중인 repo는 commit할 때 서브모듈의 contents가 아니라 commit hash를 저장한다. 즉, 열심히 서브모듈을 작업한 뒤 서브모듈의 commit 없이 메인 프로젝트만 commit 할 경우 서브모듈의 commit hash는 변경되지 않으므로, 변경사항이 반영되지 않는다. 따라서 무조건 서브모듈의 변경사항을 먼저 commit 해준 뒤에 메인 프로젝트를 commit 해줘야하는 절차가 추가된다.

그리고 서브모듈의 브랜치는 메인 프로젝트의 브랜치와 완전히 별개로 동작한다는 점도 feature 관리에 적지않은 리소스 증대를 불러온다. 만약 내가 새로운 feature를 개발하기 위해 feature/A라는 branch를 새로 만들어서 작업을 하고 있다고 가정해보자. 이때 디자인 시스템에 변경사항이 생겨서 컴포넌트의 props 구조를 변경해야했다. 그러던 중에 더 우선순위가 높은 작업이 들어와 develop branch로부터 새로운 feature/B branch를 따서 이동하면 어떻게 될까? 메인 프로젝트 내부 코드는 feature/A의 작업이 반영되지 않은 상태로 돌아가기 때문에 feature/A의 작업내용과 충돌이 일어날리 없지만, 서브모듈은 그렇지 않다. 아마 바뀐 디자인 시스템의 props 구조 때문에 에러가 발생할 확률이 높다.

// feature 작업 이전 처음 코드

//design system submodule (branch: develop)
interface CardItem {
  id:string;
  name:string;
  price:number;
}
interface CardProps {
  item:CardItem
}

//main project (branch: develop)
const cardItem = {id:'1',name:'dagym',price:1000000}
return <Card item={cardItem}/>
// feature/A 작업 중 서브모듈의 코드가 변경됨

//design system submodule (branch: develop)
interface CardItem {
  id:string;
  name:string;
  price:number;
}
interface CardProps {
  items:CardItem[]
}

//main project (branch: feature/A)
const cardItems = [
  {id:'1',name:'dagym',price:1000000},
  {id:'2',name:'partner',price:2000000}
]
return <Card items={cardItems}/>
// feature/B로 브랜치를 변경했음에도 서브모듈의 브랜치는 여전히 develop을 
// 바라보고 있으므로 서브모듈과 프로젝트간의 충돌이 발생한다.

//design system submodule (branch: develop)
interface CardItem {
  id:string;
  name:string;
  price:number;
}
interface CardProps {
  items:CardItem[]
}

//main project (branch: feature/B)
const cardItem = {id:'1',name:'dagym',price:1000000}
return <Card item={cardItem}/> // item does not exist on type CardProps

이런 문제를 방지하기 위해서는 메인 프로젝트의 브랜치를 변경할 때마다 서브모듈의 브랜치도 함께 분기하여 변경사항에 대응해줘야한다. 하지만 프로젝트에서 수정사항이 생길때마다 서브모듈의 브랜치까지 함께 관리해줘야 한다는 것은 서브모듈의 개수만큼 N배의 리소스가 더 들어간다는 의미이며, 꽤나 귀찮은 작업이 될 가능성이 높다.

그럼에도 불구하고 매력적인건 사실이다.

관리하는데 리소스가 많이 든다고 우는 소리를 했지만, 사실 그럼에도 불구하고 서브모듈은 매력적인 기술임에 틀림없다. 서브모듈 도입에 앞서 충분히 고민하고, 관리 전략을 꼼꼼하게 수립할 수 있다면 서브모듈은 좋은 선택지가 될 것이다.
사실 서브모듈 분리 번복의 결정적인 원인은 애초에 성급하게 도입을 결정했던 내 과실이 크다. 디자인 시스템을 여러 프로덕트에서 공유하겠다는 결정을 내리기 전에 '과연 지금 디자인 시스템이 충분히 성숙했는가'를 깊게 고려하지 못했다. 뷰와 로직의 분리가 완벽히 이루어지지 않은 컴포넌트들이 존재했고, 최신버전에 추가된 gap을 사용해 다짐의 디자인 시스템을 구축했기 때문에, 구버전을 사용하는 다짐파트너에서 스타일이 호환되지 못했다. 게다가 다짐파트너는 styled component를 사용했지만, 다짐은 emotion을 사용하고 있었기 때문에 라이브러리단에서 근본적으로 어긋나 있었다. 이런 부분들을 하나씩 잡아가면서 각 프로덕트의 변경사항을 조금씩 반영하다보니 어느세 두 디자인 시스템은 이미 너무 달라져버렸고, 공통 모듈의 가치를 상실한 채 관리 리소스 증대라는 단점만 남게 된 것이다.
지금와서 돌아보면 디자인 시스템을 공유하겠다는 결정을 내린 시점부터 디자인 시스템을 구축할 때 프로덕트 환경에 의존성을 갖는 요소를 의도적으로 배제해나갔어야했다. emotion 대신 styleSheet을 사용하고, gap 대신 margin을 사용하고, 비즈니스 로직을 철저하게 바깥으로 뺐어야했다. 뿐만 아니라 충분히 공유해도 될만큼 '성숙한 코드'인가에 대한 검증을 좀 더 철저히 했어야했다. 어떤 방법으로 모듈을 공유하든, 모듈이 잦은 수정이 필요한 미성숙한 상태라면 충돌이 발생할 가능성은 이에 비례하여 커질 수 밖에 없다. 이번 케이스는 이제 막 다짐의 요구사항을 맞출 수 있는 정도로만 구축된 직후 서브모듈로 분리되었고, 필연적으로 수많은 수정 사항이 발생했다. 결국 각 프로덕트에서 싱크를 맞추는 작업을 어느순간 포기해버리고 별도 브랜치로 관리하는 결정을 내릴 수밖에 없었다.
쓰다보니 괜히 안타까운 마음이 커져 마지막에는 거의 반성문처럼 되어버렸는데, 다른 분들은 조금 더 신중하게 고민하고 준비해서 서브모듈을 100% 잘 활용하길 바란다.

profile
React-Native 개발블로그

0개의 댓글