사상누각
사상누각이라는 말이 있다. 모래위의 집이라는 말로써, 튼튼한 기초와 뼈대가 있어야 추후에 무너지지 않는다는 뜻이다. 이 말은 우리의 삶 어디에서나 통용될 수 있는 말 같다. 공부도 그렇고, 운동도 그렇다. 그리고 프로그래밍 또한 그렇다. 사실 프로그래밍만큼 사상누각이라는 말이 잘 어울리는 분야는 잘 없을 것 같다는 생각이 들 정도로 프로그래밍은 튼튼한 기초와 뼈대가 무엇보다 절실한 것 같이 보인다.
굉장히 작게 설계된 프로젝트가 나중에 살이 하나씩 붙더니 이내 거대해져서 관리가 어려워지는 경우가 발생했다고 가정해보자. 작은 사이즈의 프로젝트가 왜 어느순간에 엄청나게 비대해졌을까? 이는 아마도 프로그래밍이 논리적 작업이라는 특성 때문에 그런게 아닐까 싶다. 프로그래밍에는 물리적인 제약이 없어 개발자가 아닌 분들이 보기에는 사실상의 설계 제약이 없어보인다. '아니 저번에 기능 추가 더 이상 안하신다고 했잖아요;;' 라는 변명은 통하지 않는다. 이제 나는 그걸 해야하는 사람이니까.
그런고로 처음부터 설계를 완벽히 하고 개발을 하면 좋겠으나, 문제는 처음부터 모든 것을 설계하고 시작하는 것이 상당히 어려울 수 있다. 특히 규모가 작은 회사에서 일을 한다거나, 경험이 부족한 개발자의 경우(나같은 경우)에는 모든 것을 설계하고 시작하는 것이 당연히 버겁다.
사실 글쓴이 자체가 작은 회사에 다니고, 경험이 많이 없는 개발자이다. 그래서 디렉토리 때문에 삽질을 굉장히 많이 했는데, 삽질 중 스스로 내린 결론은 '애초에 처음부터 100% 설계가 안된다면 개발 하면서 계속 설계를 고민하자.' 였다. 아래에서 내가 설명할 디렉토리 구조는 현업 중 개발과 설계를 동시에 하면서 얻은 결과이며, 삽질을 통해 얻은 지식과 편법이므로 타인이 보았을때는 굉장히 비효율적인 면이 있을 수 있다. 하지만 효율을 위한 비효율은 어느정도 감내해야 한다고 생각해보면 협업에는 큰 장점이 될 수도 있겠다.
디렉토리 설계 구조의 필요성
컴포넌트는 아무리 간단하다 할지라도 수십줄의 코드로 구성된다. 즉, 프론트엔드 소스코드는 기능 구현의 양에 따라 기하급수적으로 증가하게 된다. 방대한 양의 소스코드를 방치하면 나중에 유지보수 하는 것을 걷잡을 수 없으므로 코드를 잘 관리해야 하는데, 코드의 질도 질이지만 디렉토리가 우선적으로 관리되어야 코드 관리가 용이해진다. 디렉토리에 명확한 구조도 없고, 관리 패턴도 없다고 가정해보면 협업 과정에서 수많은 애로사항이 싹틀것임은 100% 자명하다. 그러므로 협업을 한다면 모두가 믿는 일정한 규칙이 필요하다. 즉, 각기 다른 사람들이 이러하면 이럴것이다 하는 믿음을 가지게 해주는 약간의 종교적 측면에서 디렉토리 구조를 바라볼 수 있어야 한다. 약간 오버스러웠지만 일정한 설계는 협업을 위해 반드시 필요하다는 것을 어필하고 싶었다.
나는 next.js의 app router를 이용해 개발을 하고 있으므로 이를 기준으로 설명 해보고자 한다.
Next.js 디렉토리 설계
1. App Router

next.js의 핵심은 뭐니뭐니해도 레이아웃과 라우트 기능이다. next.js 내부에서는 fs(file system)를 이용해서 디렉토리에 있는 레이아웃 파일과 페이지 파일을 조합하여 라우터로 이용한다. 즉, 조립된 모든 UI들이 화면에 보여지려면 App Router에 호출이 되어야 한다.

즉, App Router는 라우팅이라는 명명백백한 기능을 맡고 있다. 그래서 본인의 경우에는 app 디렉토리 내에 있는 모든 디렉토리와 파일들을 레이아웃과 페이지를 구성하는 요소들로만 채우기로 결정했다. 여기서 오는 장점은 확실하다. app 디렉토리 내 모든 요소들은 오로지 라우트와 레이아웃 구성에만 관여하게 되므로, 라우트와 레이아웃 관련 유지보수가 굉장히 쉬워진다.
2. components
컴포넌트는 곧 '재사용이 가능한 UI' 라고 할 수 있다. 그렇다면 하나의 페이지에서만 사용되는, 재사용될 수 없는 UI를 컴포넌트라고 할 수 있을까? 그것은 아니라고 생각이 들었다. 따라서 나는 재사용이 가능한 UI 요소들만을 모아 components 폴더에서 관리하기로 했다.

그렇다면 어느 특정 페이지에서만 활용할 수 있는 재사용이 불가능한 UI들도 고려를 해야한다. 특정 상황에서만 사용되는 UI들은 재사용 가능한 컴포넌트를 조합해서 만드는 것이 가장 이상적일 것이다. 그런 UI들은 어디서 조립하는 것이 가장 좋을까? app router에서 바로 조립해 사용하기에는 ssr, csr 등의 문제 때문에 현실적으로 어려운 부분이 존재한다.

이를 해결할 방법으로 containers라는 추상화 레벨을 도입하였다.
3. containers
컨테이너는 특정 상황에서만 쓰일 수 있는 UI를 조합하는 디렉토리로써, 공용 컴포넌트 등을 특정 페이지에 맞게 선언적으로 추상화하는 곳이라고 할 수 있다.


즉, components -> containers -> app router 순으로 추상화 레벨을 가져가게 된다.
컨테이너를 잘 활용했을 때 오는 장점은 SSR과 CSR의 명확한 구분이 가능해진다는 것이다.

App Router의 page.tsx 내부에 조립된 하나의 컨테이너를 가져다 넣는다고 가정했을 때, page.tsx는 100% SSR로 활용할 수 있다. 또한 컨테이너도 컴포넌트의 분리 조건에 따라 SSR과 CSR을 명확히 구분할 수 있게 된다. 이렇게 추상화를 염두한 상태에서 개발을 하게 되면 SSR과 CSR이 얽혀서 에러가 나는 상황을 사전에 방지할 수 있게 된다.
아래 Next.js 공식문서를 보면 이게 어떻게 가능한 것인지 알 수 있다.
Rendering: Composition Patterns | Next.js
Recommended patterns for using Server and Client Components.
nextjs.org
4. 기타 등등 (providers, hooks, constants, utils)
프론트엔드 프로젝트들은 데이터 관리, 상호작용, 기타 함수의 분리 등을 위해 hooks이나 providers, 기타 유틸 함수들을 무조건적으로 사용할 수 밖에 없다. 그리고 위에 나열된 프로바이더나 훅들은 스테로이드 호르몬처럼 침투력이 좋아서 어느 한 추상화 레벨에서만 사용해야 한다는 보장이 없으며, 동시다발적으로 사용해야할 때도 분명히 생긴다. 이렇게 어디에서든 사용할 수 있는 녀석들은 가진 역할 외에 별도의 규칙성을 찾기 어려우므로 단순하게 역할별로 디렉토리를 구성해서 모아준다.


결론 : 효율을 위한 비효율성
서울사는 사람이 서울에서 택배를 시켜도 택배는 옥천HUB로 간다. 열받는다. 불평불만이 나올 수 있다. 그냥 바로 전달해주면 될 것을 굳이 대전까지 태워 보내는가. 하지만 택배 회사의 입장에서는 모든 택배들을 한곳에 모아서 분류를 하는 것이 속 편하다. 서울 사는 사람이 서울에서 택배를 시켰다고해서 곧바로 서울에서 서울로 물건을 전달해준다면 어떻게 될까. 서울 내 집 주소는 하나가 아니므로 수많은 경우의 수와 의사소통 비용이 생길 수 있다.
프로젝트도 마찬가지인 것 같다. 회사 내 모든 구성원이 프로젝트 파일 위치의 비규칙을 다 섭렵하고 있다면 굳이 디렉토리 구조를 정리할 필요가 없다. 하지만 우리는 협업을 해야하며, 협업 대상이 한명씩 두명씩 늘어날수록 규칙이 없는 프로젝트에서는 의사소통의 비중이 두배, 세배씩 늘어날 수밖에 없다. 이것이 디렉토리 구조 설계가 필요한 이유이다.
나는 아직 작은 규모의 프로젝트를 다루고 있으므로, 지금처럼 만들면서 설계하고, 설계하면서 만들고 하는 방향성을 유지할 수가 있다. 하지만 내가 더 큰 회사에 몸을 담게 된다면 이만치 유연하게 디렉토리를 계속 설계하는 것은 불가능할 것이다. 그런고로 기회가 있을 때 구조도 많이 바꿔보고, 시행착오도 많이 겪어보아야 겠다.
'개발 일지' 카테고리의 다른 글
VSCode에서 소스 코드 저장 시 eslint + prettier가 동작하지 않는 경우 (0) | 2024.04.07 |
---|---|
프로젝트 설계 이모저모 - 프론트엔드 리포지토리 패턴 구현 (0) | 2024.02.29 |
Nextjs 프로젝트 설계 이모저모 - 집합적 사고로 레이아웃 설계하기 (0) | 2024.01.29 |
프론트엔드 설계 아이데이션 (0) | 2024.01.12 |
iOS/Android flutter 웹뷰 렌더링 엔진 이슈 해결 (1) | 2024.01.08 |
사상누각
사상누각이라는 말이 있다. 모래위의 집이라는 말로써, 튼튼한 기초와 뼈대가 있어야 추후에 무너지지 않는다는 뜻이다. 이 말은 우리의 삶 어디에서나 통용될 수 있는 말 같다. 공부도 그렇고, 운동도 그렇다. 그리고 프로그래밍 또한 그렇다. 사실 프로그래밍만큼 사상누각이라는 말이 잘 어울리는 분야는 잘 없을 것 같다는 생각이 들 정도로 프로그래밍은 튼튼한 기초와 뼈대가 무엇보다 절실한 것 같이 보인다.
굉장히 작게 설계된 프로젝트가 나중에 살이 하나씩 붙더니 이내 거대해져서 관리가 어려워지는 경우가 발생했다고 가정해보자. 작은 사이즈의 프로젝트가 왜 어느순간에 엄청나게 비대해졌을까? 이는 아마도 프로그래밍이 논리적 작업이라는 특성 때문에 그런게 아닐까 싶다. 프로그래밍에는 물리적인 제약이 없어 개발자가 아닌 분들이 보기에는 사실상의 설계 제약이 없어보인다. '아니 저번에 기능 추가 더 이상 안하신다고 했잖아요;;' 라는 변명은 통하지 않는다. 이제 나는 그걸 해야하는 사람이니까.
그런고로 처음부터 설계를 완벽히 하고 개발을 하면 좋겠으나, 문제는 처음부터 모든 것을 설계하고 시작하는 것이 상당히 어려울 수 있다. 특히 규모가 작은 회사에서 일을 한다거나, 경험이 부족한 개발자의 경우(나같은 경우)에는 모든 것을 설계하고 시작하는 것이 당연히 버겁다.
사실 글쓴이 자체가 작은 회사에 다니고, 경험이 많이 없는 개발자이다. 그래서 디렉토리 때문에 삽질을 굉장히 많이 했는데, 삽질 중 스스로 내린 결론은 '애초에 처음부터 100% 설계가 안된다면 개발 하면서 계속 설계를 고민하자.' 였다. 아래에서 내가 설명할 디렉토리 구조는 현업 중 개발과 설계를 동시에 하면서 얻은 결과이며, 삽질을 통해 얻은 지식과 편법이므로 타인이 보았을때는 굉장히 비효율적인 면이 있을 수 있다. 하지만 효율을 위한 비효율은 어느정도 감내해야 한다고 생각해보면 협업에는 큰 장점이 될 수도 있겠다.
디렉토리 설계 구조의 필요성
컴포넌트는 아무리 간단하다 할지라도 수십줄의 코드로 구성된다. 즉, 프론트엔드 소스코드는 기능 구현의 양에 따라 기하급수적으로 증가하게 된다. 방대한 양의 소스코드를 방치하면 나중에 유지보수 하는 것을 걷잡을 수 없으므로 코드를 잘 관리해야 하는데, 코드의 질도 질이지만 디렉토리가 우선적으로 관리되어야 코드 관리가 용이해진다. 디렉토리에 명확한 구조도 없고, 관리 패턴도 없다고 가정해보면 협업 과정에서 수많은 애로사항이 싹틀것임은 100% 자명하다. 그러므로 협업을 한다면 모두가 믿는 일정한 규칙이 필요하다. 즉, 각기 다른 사람들이 이러하면 이럴것이다 하는 믿음을 가지게 해주는 약간의 종교적 측면에서 디렉토리 구조를 바라볼 수 있어야 한다. 약간 오버스러웠지만 일정한 설계는 협업을 위해 반드시 필요하다는 것을 어필하고 싶었다.
나는 next.js의 app router를 이용해 개발을 하고 있으므로 이를 기준으로 설명 해보고자 한다.
Next.js 디렉토리 설계
1. App Router

next.js의 핵심은 뭐니뭐니해도 레이아웃과 라우트 기능이다. next.js 내부에서는 fs(file system)를 이용해서 디렉토리에 있는 레이아웃 파일과 페이지 파일을 조합하여 라우터로 이용한다. 즉, 조립된 모든 UI들이 화면에 보여지려면 App Router에 호출이 되어야 한다.

즉, App Router는 라우팅이라는 명명백백한 기능을 맡고 있다. 그래서 본인의 경우에는 app 디렉토리 내에 있는 모든 디렉토리와 파일들을 레이아웃과 페이지를 구성하는 요소들로만 채우기로 결정했다. 여기서 오는 장점은 확실하다. app 디렉토리 내 모든 요소들은 오로지 라우트와 레이아웃 구성에만 관여하게 되므로, 라우트와 레이아웃 관련 유지보수가 굉장히 쉬워진다.
2. components
컴포넌트는 곧 '재사용이 가능한 UI' 라고 할 수 있다. 그렇다면 하나의 페이지에서만 사용되는, 재사용될 수 없는 UI를 컴포넌트라고 할 수 있을까? 그것은 아니라고 생각이 들었다. 따라서 나는 재사용이 가능한 UI 요소들만을 모아 components 폴더에서 관리하기로 했다.

그렇다면 어느 특정 페이지에서만 활용할 수 있는 재사용이 불가능한 UI들도 고려를 해야한다. 특정 상황에서만 사용되는 UI들은 재사용 가능한 컴포넌트를 조합해서 만드는 것이 가장 이상적일 것이다. 그런 UI들은 어디서 조립하는 것이 가장 좋을까? app router에서 바로 조립해 사용하기에는 ssr, csr 등의 문제 때문에 현실적으로 어려운 부분이 존재한다.

이를 해결할 방법으로 containers라는 추상화 레벨을 도입하였다.
3. containers
컨테이너는 특정 상황에서만 쓰일 수 있는 UI를 조합하는 디렉토리로써, 공용 컴포넌트 등을 특정 페이지에 맞게 선언적으로 추상화하는 곳이라고 할 수 있다.


즉, components -> containers -> app router 순으로 추상화 레벨을 가져가게 된다.
컨테이너를 잘 활용했을 때 오는 장점은 SSR과 CSR의 명확한 구분이 가능해진다는 것이다.

App Router의 page.tsx 내부에 조립된 하나의 컨테이너를 가져다 넣는다고 가정했을 때, page.tsx는 100% SSR로 활용할 수 있다. 또한 컨테이너도 컴포넌트의 분리 조건에 따라 SSR과 CSR을 명확히 구분할 수 있게 된다. 이렇게 추상화를 염두한 상태에서 개발을 하게 되면 SSR과 CSR이 얽혀서 에러가 나는 상황을 사전에 방지할 수 있게 된다.
아래 Next.js 공식문서를 보면 이게 어떻게 가능한 것인지 알 수 있다.
Rendering: Composition Patterns | Next.js
Recommended patterns for using Server and Client Components.
nextjs.org
4. 기타 등등 (providers, hooks, constants, utils)
프론트엔드 프로젝트들은 데이터 관리, 상호작용, 기타 함수의 분리 등을 위해 hooks이나 providers, 기타 유틸 함수들을 무조건적으로 사용할 수 밖에 없다. 그리고 위에 나열된 프로바이더나 훅들은 스테로이드 호르몬처럼 침투력이 좋아서 어느 한 추상화 레벨에서만 사용해야 한다는 보장이 없으며, 동시다발적으로 사용해야할 때도 분명히 생긴다. 이렇게 어디에서든 사용할 수 있는 녀석들은 가진 역할 외에 별도의 규칙성을 찾기 어려우므로 단순하게 역할별로 디렉토리를 구성해서 모아준다.


결론 : 효율을 위한 비효율성
서울사는 사람이 서울에서 택배를 시켜도 택배는 옥천HUB로 간다. 열받는다. 불평불만이 나올 수 있다. 그냥 바로 전달해주면 될 것을 굳이 대전까지 태워 보내는가. 하지만 택배 회사의 입장에서는 모든 택배들을 한곳에 모아서 분류를 하는 것이 속 편하다. 서울 사는 사람이 서울에서 택배를 시켰다고해서 곧바로 서울에서 서울로 물건을 전달해준다면 어떻게 될까. 서울 내 집 주소는 하나가 아니므로 수많은 경우의 수와 의사소통 비용이 생길 수 있다.
프로젝트도 마찬가지인 것 같다. 회사 내 모든 구성원이 프로젝트 파일 위치의 비규칙을 다 섭렵하고 있다면 굳이 디렉토리 구조를 정리할 필요가 없다. 하지만 우리는 협업을 해야하며, 협업 대상이 한명씩 두명씩 늘어날수록 규칙이 없는 프로젝트에서는 의사소통의 비중이 두배, 세배씩 늘어날 수밖에 없다. 이것이 디렉토리 구조 설계가 필요한 이유이다.
나는 아직 작은 규모의 프로젝트를 다루고 있으므로, 지금처럼 만들면서 설계하고, 설계하면서 만들고 하는 방향성을 유지할 수가 있다. 하지만 내가 더 큰 회사에 몸을 담게 된다면 이만치 유연하게 디렉토리를 계속 설계하는 것은 불가능할 것이다. 그런고로 기회가 있을 때 구조도 많이 바꿔보고, 시행착오도 많이 겪어보아야 겠다.
'개발 일지' 카테고리의 다른 글
VSCode에서 소스 코드 저장 시 eslint + prettier가 동작하지 않는 경우 (0) | 2024.04.07 |
---|---|
프로젝트 설계 이모저모 - 프론트엔드 리포지토리 패턴 구현 (0) | 2024.02.29 |
Nextjs 프로젝트 설계 이모저모 - 집합적 사고로 레이아웃 설계하기 (0) | 2024.01.29 |
프론트엔드 설계 아이데이션 (0) | 2024.01.12 |
iOS/Android flutter 웹뷰 렌더링 엔진 이슈 해결 (1) | 2024.01.08 |