← All writing

ARCHITECTURE

변화에 강한 프론트엔드 구조 만들기

변화에 강한 프론트엔드 구조 만들기

제품은 계속 변합니다. 오늘의 완벽한 구조가 다음 분기의 요구사항까지 완벽하게 설명할 수는 없습니다. 그래서 좋은 프론트엔드 구조는 변화를 막는 구조가 아니라, 변화의 범위를 제한하는 구조에 가깝습니다.

화면보다 도메인으로 나누기

컴포넌트를 페이지 이름만으로 분류하면 기능이 늘어날수록 의존 관계가 흐려집니다. 대신 사용자가 수행하는 일과 제품의 도메인을 기준으로 경계를 세웁니다.

// 기능의 책임이 드러나는 구조
features/
  article/
  search/
  profile/

의존성은 한 방향으로

공통 모듈이 특정 기능을 다시 참조하기 시작하면 변경의 영향 범위를 예측하기 어렵습니다. 작은 규칙이지만, 의존 방향을 지키는 것만으로도 리팩터링 비용이 크게 줄어듭니다.

구조의 목적은 파일을 예쁘게 정리하는 것이 아니라 팀이 안전하게 결정할 수 있게 하는 것입니다.

마지막으로

추상화는 반복이 발견된 뒤에 시작해도 늦지 않습니다. 먼저 명확하게 만들고, 실제 변화의 패턴을 관찰한 다음 공통점을 꺼내세요.

경계를 먼저 합의하기

구조를 바꾸기 전에 팀이 먼저 맞춰야 할 것은 기술 이름보다 경계입니다. 어떤 코드는 화면의 표현을 책임지고, 어떤 코드는 사용자의 행동을 해석하며, 어떤 코드는 서버와의 통신을 담당하는지 구분합니다. 이 합의가 없으면 폴더 구조를 아무리 정리해도 새로운 기능이 들어올 때마다 같은 논쟁이 반복됩니다.

작은 기능도 처음부터 완벽한 계층으로 나눌 필요는 없습니다. 다만 코드가 어느 경계를 넘고 있는지 의식하면, 나중에 분리해야 하는 순간을 더 빠르게 알아챌 수 있습니다.

화면의 책임

화면 컴포넌트는 사용자가 무엇을 보고 무엇을 누를 수 있는지 표현하는 데 집중합니다. 데이터가 어디에서 왔는지, 저장 요청이 어떤 방식으로 실패하는지는 화면이 직접 알 필요가 없습니다. 화면이 이 정보를 너무 많이 알게 되면 같은 기능을 다른 화면에서 재사용하기 어려워집니다.

표현을 담당하는 코드는 가능한 한 입력과 출력이 분명해야 합니다. 같은 입력이라면 같은 화면을 보여 주고, 이벤트가 발생하면 의도를 외부로 전달합니다. 이 단순한 원칙이 컴포넌트 테스트와 디자인 변경을 편하게 만듭니다.

기능의 책임

기능 단위에서는 사용자의 행동과 제품 규칙을 연결합니다. 예를 들어 글을 북마크하는 기능이라면 로그인 상태 확인, 저장 요청, 낙관적 업데이트, 실패 시 되돌리기 같은 흐름이 이곳에 모입니다. 화면은 이 흐름을 호출할 뿐 세부 규칙을 다시 구현하지 않습니다.

이렇게 분리하면 기능이 성장해도 화면은 비교적 얇게 유지됩니다. 같은 기능을 목록과 상세 화면에서 사용해야 할 때도 규칙을 복사하지 않아도 됩니다.

데이터 흐름을 단순하게 유지하기

복잡한 화면은 대체로 데이터가 여러 방향으로 이동할 때 생깁니다. 어느 컴포넌트가 값을 바꾸는지, 변경된 값이 어디까지 영향을 주는지 알 수 없으면 작은 수정도 불안해집니다. 데이터를 한 방향으로 흐르게 만들면 이 불안을 줄일 수 있습니다.

먼저 서버에서 받은 원본 데이터와 화면에서 잠깐 필요한 상태를 구분합니다. 원본 데이터는 한 곳에서 관리하고, 모달 열림 여부나 입력 중인 문장처럼 일시적인 값은 화면 가까이에 둡니다. 두 종류의 상태를 섞지 않는 것만으로도 코드를 읽기가 쉬워집니다.

서버 상태와 화면 상태

서버 상태는 다른 사용자나 다른 기기에서 바뀔 수 있으며, 다시 요청해서 최신 상태를 확인해야 합니다. 반면 화면 상태는 현재 사용자의 조작 과정에서만 의미를 갖습니다. 둘은 갱신 방식도 실패 처리 방식도 다르기 때문에 같은 저장소에 무심코 넣으면 복잡도가 커집니다.

서버 상태를 수정할 때는 성공, 로딩, 실패 상태를 명확히 드러내는 편이 좋습니다. 사용자가 저장 버튼을 두 번 누르지 않도록 하고, 실패했을 때 왜 이전 화면으로 돌아왔는지도 알 수 있게 합니다.

변경은 가까운 곳에서 시작하기

상태를 전역으로 올리는 일은 편리해 보이지만, 실제로 그 값을 필요로 하는 범위를 넓힐 수 있습니다. 처음에는 가장 가까운 공통 부모에 상태를 두고, 세 곳 이상에서 공유해야 할 때 전역 상태로 옮기는 편이 안전합니다.

이 기준은 정답이라기보다 변경 비용을 관찰하기 위한 장치입니다. 상태를 옮길 때마다 누가 그 값을 읽고 바꾸는지 목록으로 적어 보면, 전역화가 정말 필요한지 판단하기 쉬워집니다.

점진적으로 개선하기

좋은 구조는 한 번의 대규모 리팩터링으로 완성되지 않습니다. 제품 일정 속에서 조금씩 개선할 수 있어야 실제 팀의 구조가 됩니다. 새 기능을 만들 때 가까운 코드 한 조각을 더 명확하게 하는 정도부터 시작해도 충분합니다.

리팩터링의 목표를 코드의 모양이 아니라 다음 변경을 얼마나 쉽게 할 수 있는지로 잡아 보세요. 새로운 요구사항을 구현하는 시간이 줄고, 영향 범위를 설명하기 쉬워졌다면 구조는 이미 좋아지고 있는 것입니다.

작은 규칙을 문서화하기

팀이 자주 반복해서 이야기하는 규칙은 짧게라도 문서로 남기는 것이 좋습니다. 의존성 방향, 파일 이름, API 호출 위치처럼 판단 기준이 되는 내용을 기록하면 새로 합류한 사람도 같은 방식으로 코드를 작성할 수 있습니다.

규칙은 많을수록 좋은 것이 아닙니다. 실제로 지킬 수 있는 규칙만 남기고, 예외가 자주 생긴다면 규칙 자체가 현재 제품에 맞지 않는지 다시 살펴보아야 합니다.

변경을 검증하는 방법

구조 변경 뒤에는 기능이 여전히 동작하는지 확인하는 테스트가 필요합니다. 모든 코드를 단위 테스트로 덮기보다, 사용자에게 중요한 흐름부터 자동으로 확인하는 편이 효과적입니다. 로그인, 저장, 결제처럼 실패했을 때 영향이 큰 흐름이 우선입니다.

테스트는 구조를 고정하는 도구가 아니라 안전하게 바꿀 수 있게 해 주는 도구입니다. 테스트가 있다면 더 작은 단위로 개선하고, 실패하면 즉시 원인을 좁힐 수 있습니다.

마무리

프론트엔드 구조는 미래를 정확히 맞히기 위한 설계도가 아닙니다. 변화가 생겼을 때 어디를 손대야 하는지 알 수 있도록 현재의 선택을 선명하게 만드는 방법입니다. 경계를 작게 세우고, 데이터 흐름을 단순하게 유지하고, 실제 변경을 통해 규칙을 다듬어 보세요.