주니어 프론트엔드 개발자의 개발 회고
안녕하세요.
현재 웹 에이전시 똑똑한개발자에서 일하고 있는 주니어 프론트엔드 개발자입니다.
웹 에이전시 특성상 비교적 짧은 기간 내에 다양한 프로젝트에 참여하고 또 완성할 수 환경과
자유로운 저희 프론트엔드 개발 팀의 환경 덕분에 여러 고민과 시도를 하며 1년이 넘는 시간이 흘렀습니다.
그리고 그동안 고민했던 개발과 일에 대한 생각을 하나씩 풀어가볼까 합니다.
아무튼 개발적인 고민중 하나를 꼽자면 디자인 패턴이었는데요, 리액트를 처음 접하던 시기부터 고민하던 문제이며, 또 글을 적는 지금까지도 고민이기 때문입니다.
그래서 아토믹 디자인 패턴에 대해 고민하고 사용한지 약 1년이 넘어가는 시점에 제가 적용중인 아토믹 패턴의 장,단점과 생각들을 회사 팀원분들께 세션을 열어 공유할 수 있었습니다.
아마 다른 팀원분들께 도움이 되었는지는 잘 모르지만 또 저와 비슷한 고민을 가진분들께 도움을 드릴 수 있을까 싶어 이렇게 글로도 적어봅니다.
이번 주제인 아토믹 디자인 패턴에 대한 정의는 사실 훌륭한 분들이 이미 잘 정리해 주셨습니다. 저도 이 분들의 글 덕분에 보다 쉽게 이해할 수 있었고, 이 글들 보다 개념을 더 잘 정리할 자신은 없기 때문에 아주 간략한 정의와 참조들을 남겨놓고 넘어가겠습니다.
아토믹 디자인 패턴은 원자, 분자와 같은 작은 단위들이 모여 더 큰 대상을 만드는 것 처럼 코드를 잘게 쪼개고 합치며 재사용성을 늘리기 위해 고안된 디자인 패턴입니다.
이러한 아토믹의 단위는 크게 5가지,
Atom (원자) - Molecule(분자) - Organism (유기체) - Template (탬플릿) - Page (페이지) 로 분류됩니다.
아토믹 디자인 패턴이 등장한지는 지는 꽤 오랜 시간이 지났지만, React가 발전함에 따라 더 주목받고 사용되게 되었습니다. 컴포넌트를 쪼개고 부분적으로 렌더링하는 SPA (Single Page Application)의 목적에 부합해서가 아닐까 생각합니다.
그러나 위 목적에도 불구하고 바이블같은 디자인 패턴이 되지 못한 이유는 위 5개의 단위를 나누는 기준이 모호한 개념적인 패턴(Mental Model)이기 때문입니다.
아토믹 패턴을 누가 사용하느냐에 코드를 나누는 기준이 다 다르기 때문에, 정말 명확한 기준을 세우지 않는다면 코드를 쪼개는 사람도, 이를 보고 사용하는 사람도 헷갈리는 상황이 종종 발생합니다.
그럼에도 아토믹 패턴을 사용한 이유는 제가 느끼기에 Javascript 진영의 다른 디자인 패턴 또한 모호한 부분이 많다고 생각하기도 했고, 디자인 패턴의 의의와 개념을 이해하기에는 다른 패턴보다 좀 더 쉬워서 더 관심을 가졌기 때문입니다.
앞서 말한 듯 아토믹 디자인 패턴은 개념적인 패턴이기 때문에 사용하기 위한 규칙을 직접 세워야만 했습니다. 많은 레퍼런스를 참고하기 했지만 레퍼런스의 기준과 저의 기준은 또 달랐는데요, 이는 저희 회사의 개발 환경도 한 몫 했습니다.
저희 회사는 현재 Chakra UI 라는 스타일링 라이브러리를 사용하고 있는데요, 개인적으로 이전에 다른 스타일링 라이브러리를 쓰면서 미리 구현된 컴포넌트가 적거나, 커스텀이 어려운 문제를 항상 겪었었는데 이 두 부분을 완벽히 해결해주는 최고의 라이브러리가 아닌가 생각이 됩니다.
아무튼 이 Chakra UI의 컴포넌트는 다른 아티클에서 정의하는 Atom의 컴포넌트 단위를 거의 구현해놓은 덕분애 저는 ChakraUI의 컴포넌트를 최대한 활용하되, 제가 정의하는 Atomic 규칙에서 제외시키고좀 더 의미있는 단위로 분류할 수 있었습니다.
규칙은 크게 전체적으로 로직을 관리하는 Logic Rule과, atomic 컴포넌트의 단위를 나누는 Component Rule 두가지로 나뉩니다.
1. Business Logic은 모두 최상단 컴포넌트에
해당 규칙은 세운 이유은 간단했습니다. 제가 데이터의 흐름이 명확한 코드 스타일을 좋아하기도 하지만 아직 컴포넌트의 쪼개는 단위가 명확하지 않은 상황에서 비즈니스 로직까지 여럿으로 흩어져있다면 추후 유지/보수 측면에서 정말 복잡할 것이라 느꼈기 때문입니다.
이렇게 최상단에서 선언한 로직들은 Props로 하위 컴포넌트까지 전달해줍니다.
함수의 경우 90%이상 핸들러 함수로 선언하여 하위 컴포넌트로 전달하지만,
onChange (value) => setState(value)
같은 아주 단순한 핸들러 함수는 setState 자체를 전달하기도 합니다.
이미 최상단 컴포넌트에 수십개의 핸들러가 있다보니 불필요한 함수 선언을 줄이기 위함도 있고, setState를 넘기는 것 또한 컴포넌트의 목적을 파악하는데에 문제가 되지 않는다고 생각했습니다
2. Props 네이밍 규칙에 따라 컴포넌트 구조를 명확하게
재사용성이 높은 전역 컴포넌트라면 네이밍을 러프하게 onClickDate, 특정 영역에서만 사용되어 재사용성이 적은 지역 컴포넌트라면 컴포넌트라면 네이밍을 명확하게 onClickSaveMainEpisodeOrderButton 처럼 사용합니다.
또한 하위의 하위 컴포넌트까지 props를 전달할 때 props의 이름은 전달할때나 받을 때 모두 동일하게 선언합니다.
이는 여러 컴포넌트를 건너 props를 전달 할 때 헷갈리지 않게 하기 위함입니다.
같은 네이밍의 props를 따라가다보면 금세 컴포넌트 구조를 파악할 수 있거든요.
Component Rule은 공통적인 몇가지 규칙과 함께 아토믹 디자인 패턴의 단위를 정의하는 규칙들입니다.
1. 컴포넌트 단위는 Atom / Molecule / Organism 세 단계만 규칙 정의
현재 저희는 Next.js 프레임워크를 사용하고 있습니다. 때문에 page단은 next page와 1:1대응되며, Template 또한 page와 1:1 대응하는 Container 라는 구조가 있기 때문에 두 단위는 정할 필요가 없었습니다.
2. 공용 컴포넌트는 각 단위별 폴더에 컴포넌트 정리, 지역 컴포넌트는 규칙을 적용하지 않음.
공용 컴포넌트는 #Atom 과 같이 #(해시)를 붙여서 정리했습니다. 그러나 지역 컴포넌트까지 해당 규칙을 적용한다면 현재 저희 보일러 플레이트의 폴더 구조가 너무 깊어지거나, 매 컴포넌트 마다 단위를 고민하는데에 많은 시간이 소요될 것 같았기 때문에 지역 컴포넌트는 해당 규칙을 제외했습니다.
3. Chakra Theme 활용
사실 규칙이라고 하기 애매한데요, 저희가 사용하는 Chakra UI의 Theme 설정만 잘 잡아줘도 많은 공용 컴포넌트가 불필요해졌습니다. 이를 최대한 활용하여 CustomInput 같은 불필요한 컴포넌트 생성을 줄이는 것이 코드 파악에 유리했습니다.
컴포넌트의 가장 작은 단위입니다.
보통 단일 데이터로 UI를 그리거나, 일부 로직과 같은 한가지 목적을 갖고 구현됩니다.
단순한 만큼 재사용성이 높습니다.
Text, Input, Flex 같은 기본 컴포넌트들은 Chakra UI에서 제공해주기 때문에 보통의 Atoms보다 더 큰 단위를 의미합니다.
상태를 관리하는 등 일부 비즈니스 로직과 UI가 함께 구현되거나
반복문과 특정 레이아웃 등 부분적으로 완성된 UI 컴포넌트를 정의했습니다.
다른 아티클의 Organism 일부가 저는 Molecule로 볼 수 있을 것 같아요.
아무튼, 이러한 분자 단위의 컴포넌트는 외부에서 값이나 상태 주입이 거의 필수적이고,
비즈니스 로직 동작을 위한 핸들러 또한 외부에서 주입해서 사용합니다.
많은 컴포넌트가 이에 해당되지만,
대표적으로 offset pagination 로직과 UI를 함께 처리하는 Pagination 컴포넌트가 있습니다.
API호출, routing 등 중요한 비즈니스 로직이 포함된 컴포넌트 입니다.
복잡도나 단위 자체는 Molecule 과 거의 흡사하지만, 중요한 비즈니스 로직의 포함 여부로 Organism 와
Molecule 이 나뉩니다.
오로지 컴포넌트의 복잡도로만 나누기에는 너무 모호하기 때문에 이러한 기준을 적용했습니다.
이러한 기준은 최근에서야 적용했는데,제 예전 코드를 뒤져보다가 분류가 뒤죽박죽이라고 느끼는 부분이 많았거든요.
해당 컴포넌트의 경우, 선택한 언어에 따라 routing을 진행하지만 routing을 진행하는 핸들러를 넘기지 않습니다.
path와 옵션만 넘기면 내부 핸들러 로직에 따라 기능까지 같이 구현되었습니다.
이 외에 너무 많은 규칙들은 오히려 지키기 어렵기 때문에 중요한 몇개의 규칙만 적용하였습니다.
나머지는 저희 회사의 전체적인 코드 컨벤션을 따르거나, 개개인의 특징으로 이해해도 된다고 생각했습니다.
그리고 이러한 규칙을 세우고 또 바꿔가며 며칠간 작업하다보니 장, 단점이 보였습니다.
1. 같은 레벨의 컴포넌트를 사용할 때 구조적인 고민 발생
다른 디자인 패턴에 비해 아토믹 디자인 패턴은 단위적인 위계가 확실한 편입니다. 때문에 Atom을 활용하여 Molecule을 만드는 것은 자연스럽지만 Molecule을 활용하여 Molecule을 만든다면 아무래도 컴포넌트 위계가 부자연스럽게 생각되었습니다.
Class의 부모-자식 관계처럼 코드상으로 동작하지 않는 부분은 아니지만 이러한 위계구조가 명확하지 않은 코드가 많아진다면 그 때는 또 다른 컴포넌트 기준이 필요할 것 같았습니다.
해당 달력은 우측 CustomCalender의 컴포넌트를 import하여 DateSelectPopover 가 구현되었습니다.
그러나 둘 다 핸들러까지 포함되어 Organism으로 분류되어 문제가 있다고 느꼈습니다.
2. 과도한 props drilling이 발생
이건 사실 아토믹 자체라기보단 아토믹 패턴을 잘 쓰기 위한 제 코드 스타일로부터 비롯된 문제입니다.
앞서 말했듯 최상단에서 하위 컴포넌트로 props를 넘겨주는 구조는 다른 전역 상태나 context 를 사용하는 것 보다 훨씬 명확하게 데이터 흐름을 파악할 수 있습니다. 이러한 장점에도 역시 5개 이상의 컴포넌트를 지나가며 흐름을 파악하는건 헷갈리기 마련입니다.
3. 최상단 컴포넌트가 너무 비대해지는 경우가 발생
사실 이게 가장 큰 문제였습니다.
유틸성인 순수함수라거나, UI를 위한 공통 로직은 분리하긴 합니다만, 결국 이 로직을 선언하는건 대부분 최상단 컴포넌트 입니다.
때문에 컴포넌트를 적절히 리팩토링 하더라도, 여러개의 API를 호출해야 하는 페이지는 코드 1천줄은 곧잘 넘어갑니다. 수천줄이 되는 코드는 아무리 변수의 이름이 명확하고 선언 순서가 명시되어있다 하더라도 코드를 파악하는건 불편한 일이었습니다.
또한 Tab 처럼 하나의 페이지임은 분명하지만 로직을 거의 공유하지 않는 개별적인 단위의 컴포넌트는 최상단 컴포넌트를 정의가 모호했습니다.
Tab을 최상단으로 볼 것인지, 그럼에도 page를 최상단으로 볼 것인지… 규모나 로직에 따라 결정하여 코드를 짜고 있지만 항상 기분좋은 고민은 아니었습니다. 심지어 Tab단위에서 로직을 전부 구현했는데 그 상단 page단위에서 하위 Tab 전체에 관여하는 기획을 뒤늦게 찾았을 때에는…정말 아찔했습니다.
아무튼 위와 같은 단점이 있음에도 이러한 디자인 패턴을 유지했던 데에는 당연히 장점도 있어서 입니다.
1. 추후 유지보수에 유리함
아토믹 디자인 패턴을 적용 이후 로직이 어디서 선언되고, 어떤 흐름으로 구현되는지 명확하기 때문에 코드를 수정하거나 추가하는데에 수월했습니다.
남의 코드는 물론이고 며칠만 지나도 제 코드 또한 낯설게 느껴지지만, 현재 패턴으로 코드를 짰을 때에는 몇개월이 지난 시점에도 큰 어려움 없이 어떻게 코드가 짜여있는지 파악하고 수정까지 오래 걸리지 않았습니다.
아, 물론 디자인 패턴을 정립하는 과도기 시점의 코드를 볼 때에는 당장 한달만 지나도 혼란이 말이 아니었습니다..
이러한 코드 스타일이 큰 단위에서 작은단위까지 자연스럽게 파악할 수 있는 아토믹 패턴에선 좀 더 매력적이었던 것 같구요.
2. 구조화된 설계 및 컴포넌트 의도 파악이 용이
사실 컴포넌트를 정말 잘 설계, 구현하고 이러한 컴포넌트의 상세한 문서화까지 이루어진다면 어떤 디자인 패턴을 사용하거나, 또 사용하지 않아도 아무런 문제가 없습니다.
그러나 그렇지 않았기 때문에 협업하는 종종 해당 코드가 구현되어 있는지 다른 분들께 여쭤보기도 하고 구현되어있던 코드가 있더라도 정확한 사용법이나 side effect까지 한번에 파악하기에는 어려움이 있었습니다.
이러한 점을 아토믹 디자인 패턴을 사용함으로써 해당 컴포넌트에 구현된 로직이나 목적을 파악하는데에 좀 더 용이하게 만들었습니다.
폴더 또한 구조화되어 있어서 하나의 폴더가 비대해짐을 막는 정리 효과는 덤 임니다.
아토믹 디자인 패턴을 사용하면서 많은 이점도 있었지만, 최근 구조와 관련된 큰 이슈가 한번 생기면서 “현재 구조와 스타일을 버려야하나…”에 대한 고민이 생겼었습니다.
앞서 단점에서 얘기했던 것 처럼, 최상단 컴포넌트로 Tab 단위를 산정하고 로직을 구현했는데, 그 상단 컴포넌트에서 로직을 제어할 일이 생겼기 때문입니다.
물론 그 상단 컴포넌트로 로직을 전부 올리는 방식이나 context, 전역상태 등 여러 방법으로 리팩토링하여 해결할 수 있었지만, 프로젝트가 너무 타이트하게 진행되기도 했고 전역에서 관리하기 위해 모든 코드만 몇천줄이 될 만큼 꽤 로직들이 많이 붙은 페이지였습니다.
결국 상위 컴포넌트에서 필요한 값들만 급하게 상위 컴포넌트로 올리는 방법으로 일단락 했습니다만…가장 중요하게 생각하는 최상단 컴포넌트에서 로직 라는 로직이 깨진 순간이었습니다. 리팩토링도 규칙도 뭔가 제대로 지키지 못한 상황이 꽤나 스트레스였거든요.
아무튼…이렇게 아토믹 디자인 패턴과 이별할 뻔 했지만 결국 좀 더 사용해보기로 결론 내렸습니다.
이유는 현재 React에 많이 사용되는 hooks와 같은 다른 디자인 패턴 또한 규칙을 정의하는데에 모호한 부분이 많고, 다른 동료분들께 여쭤보고 고민해봐도 명확한 해답은 내리지 못했기 때문입니다. 그럴바에는, 어차피 현재 아토믹 디자인 패턴 또한 장, 단점을 명확하게 느꼈으니 이를 좀 더 보완할만한 방법으로 고민해보는 것이 장기적으로 더 낫겠다 싶었습니다.
앞으로 좀 더 고민해 볼 부분은 아래와 같습니다.
context 등을 적절히 활용하여 props drilling을 줄이고 좀 더 편하게 사용할 수 있는 방법
Atom - Molecule - Organism의 좀 더 명확하고 동료분들도 납득 가능한 규칙
분리한 컴포넌트의 재사용성을 극대화 할 수 있는 방법
다행히 멋진 동료분들이 의견을 주시기도 하셨고, 또 순수 컴포넌트와 headless UI 처럼 컴포넌트를 짜는 방법론에 대해 친절하게 세션을 열어주신 많은 동료분들 덕분에 더 나은 구조에 대해서도 고민하고 있습니다.
짧은 지식이지만 누군가에게 생각할거리가 되었으면 좋겠고, 혹시나 피드백을 남겨주신다면 감사히 받겠습니다.
감사합니다.