본문 바로가기
Resource/웹 프론트엔드

[프론트엔드 아키텍쳐] 프론트엔드 아키텍쳐 도입 일기

by 우창욱 2024. 4. 1.

프론트엔드에도 아키텍쳐 구조를 도입해보자

LUMIR SARDIP WEB-GIS

들어가며

안녕하세요. 이번 글에서는 루미르의 SARDIP WEB-GIS 프로젝트(2024.01.02 ~ 2024.02.28) 개발을 진행하면서 겪은 어려움들과 이 어려움을 해결하기 위해, 프론트엔드에서는 아키텍쳐를 어떻게 구성하면 좋을지에 대한 고민을 담은 내용을 정리해보려고 합니다.

 

이번 프로젝트의 경우, 백엔드 개발자인 사수님과 프론트엔드 개발자, 저 두명이서 진행하게 된 프로젝트입니다. (사내 웹 개발자가 두명뿐이어서 당장 투입할 수 있는 인력들은 모두 참여하게 된 것이라고 보시면 되겠습니다.) 프로젝트의 프론트엔드 개발자가 한 명 뿐이라 힘들었다기 보다는 오히려 혼자 하나의 프로젝트를 구성하다 보니 이것저것 많이 시도할 수 있는 기회를 얻어서 더 좋았다고 생각했습니다. 개발해야할 기능은 GPT가 잘 작성해주기 때문에 프로젝트 구조적으로 고민할 수 있는 시간이 많았어서 많은 공부가 되었던 프로젝트였습니다.

겪은 어려움들

1. 코드 관리의 어려움

개발을 처음 시작하면서 작은 기능들을 하나씩 추가할 때는 아직 작성된 코드가 많지 않았기에, 당연하게도 코드 관리에 대한 복잡도가 그렇게 크지 않았습니다. 일단 어딘가에 코드를 추가하고, (ex. constants/sidebars.ts, store/map.ts, ...) 거기서 관리를 진행하면 되었으니까요. 하지만 점차 기능이 추가되고, 프로젝트 기획 단계에서 생각했던 방향과는 조금 다르게 수정해야할 부분이 생기면서 코드 유지관리에 문제가 생기기 시작했습니다.

자그마치 281줄이나 되어버린 zustand 전역 상태 파일

???: 전역 상태관리 도구인 zustand를 사용하면 props drilling을 하지 않아도 되니까 이 훅이 필요한 컴포넌트에서 이걸 호출해서 사용하면 간단하겠네!

 

라고 (단순하게) 생각하면서 전역 상태관리 파일에서 정의한 훅들을 필요한 컴포넌트에서 호출했습니다. 이후 테스트를 작성해야할 때 이렇게 작성한 부분들이 얼마나 성가신 존재로 다가올 지에 대해서는 깊은 고민을 하지 않았습니다.

그리고 어플리케이션의 Map을 다루면서 상태를 관리해야 하는 코드들을 모두 map.ts 파일에서 관리하도록 작성하고 있었는데 그러다보니 Map과 관련되어는 있지만, 전역 상태에서 관리할 필요가 없어 보이는 코드들임에도 일단 map 관련된 코드들이 필요하니까 이 파일에서 관리하는 게 좋겠다고 생각해서 추가하다보니 파일의 길이가 너무 길어지게 되었습니다. 이후 요구사항이 변경되기도 하고, 추가도 될 때 개발 속도가 점점 현저히 느려지고 있다는 것을 깨닫게 되었습니다.

 

2. 테스트 코드 작성의 어려움

프론트엔드에서는 로직과 관련된 테스트 코드를 작성할 때, 크게 단위 테스트, 통합 테스트, E2E 테스트 3가지로 작성하게 됩니다. E2E 테스트는, 실제 비즈니스 프로세스와 관련된 테스트이기 때문에 실제 개발 단계에서는 적용이 힘들고 어느정도 개발이 마무리 된 상태에서 적용하게 되는 테스트이므로 논외로 하겠습니다. 어느정도 개발 시간을 보장 받을수만 있다면, TDD 프로세스를 도입하면서 테스트 코드를 작성하는 것이 유지보수에 장점이 있고, 어플리케이션의 안정성에도 도움이 되기 때문에 앱의 프로덕션을 고려하는 레벨에서는 TDD를 도입하는 것이 매우 중요하다고 할 수 있습니다. 

그러나 앞서 전역 상태관리를 도입하면서 큰 고민 없이 해당 훅이 필요한 컴포넌트에서 직접 훅을 호출하면서 코드를 작성하게 되니, 테스트 코드를 작성하기가 어려워지는 문제가 발생했습니다. 이런 상황에서 테스트를 하기 위해서는 외부 의존성을 매번 모킹해서 주입해주어야 한다는 것이었습니다. 예를 들어, 아래처럼 "CategoryButtons의 클릭 로직이 정상적으로 실행되는가" 에 대한 테스트를 진행한다고 했을 때, useRouter 훅과, usePathname 훅을 테스트 코드 프레임워크(ex. vitest, jest)의 spy() 함수를 사용해서 훅을 모킹해주어야 합니다.

 

 

이 로직은 굳이 여기서 훅을 호출할 필요가 없었습니다. 상위 컴포넌트에서 훅을 한번만 호출하고, 하위 컴포넌트로 props를 전달하면 하위 컴포넌트는 이 props를 vi.fn()과 같이 간단하게 모킹하면서 함수의 호출 여부만 판단하는 단위 테스트만 작성하면 이 컴포넌트의 기능을 검증할 수 있게 되므로 복잡한 로직을 테스트할 필요가 없었습니다. 안타깝게도 프로젝트 개발 단계에서는 테스트 코드의 필요성과 개념에 대한 깊이있는 학습이 되어있지 못한 상황이어서, 이런 단점을 이후에 테스트 코드에 대한 개념이 쌓이고, 추가 요구사항을 쳐내야 하는 과정에서 점차 깨닫게 되었습니다.

 

백엔드 개발에서는 TDD가 기본으로 자리잡았다고 알고 있는데 프론트엔드에서도 여건이 된다면 무조건 TDD를 도입하는 것이 코드 유지보수를 위해서 정말 중요한 것 같습니다.

 

3. UI 렌더링 컴포넌트 관리의 어려움

회사 디자이너가 없고, 따로 디자인을 참고할 만한 템플릿이 없던 상황이었습니다. 그리고 웹 개발자 두명이서 프로젝트 기획을 하며, 어떤 UI 디자인을 적용해야할 지 많은 고민이 있었는데 공통 컴포넌트부터 차근차근 구분하면서 적용하기엔 시간적으로 부족한 상황이었습니다. 그래서 빠르게 개발하면서 변하는 요구사항에 맞게 디자인 하기 편한 tailwindcss를 도입했었는데 어플리케이션의 디자인이 점점 복잡해지면서 클래스명이 너무 길어지고 코드 가독성이 떨어지는 단점이 발생하고 있었습니다. 

해결책

컴포넌트 모듈화

이 아키텍쳐 구조는 백엔드 사수님이 제안해주셨는데, Nest.js의 폴더 구조를 프론트엔드의 컴포넌트에 적용해보는 실험적인 방법론입니다. 간단하게 구조를 설명드리겠습니다.

 

/Calendar 컴포넌트

|__  /components

|____  Calendar.container.tsx: Calendar 컴포넌트의 로직을 중점적으로 담당하고 Calendar 컴포넌트를 렌더링한다. (추후 컴포넌트 관련 로직은 service 폴더로 따로 분리한다.)

|____  Calendar.component.tsx: Calendar 컴포넌트에서 사용되는 단위 컴포넌트들을 구분하여 작성한다. 해당 컴포넌트에서 공통되게 사용하는 상수나, 변수를 멤버 변수로 정의하여 공유한다. (컴포넌트는 추후 단위 테스트로 검증한다.)

이렇게 Calendar.component.tsx 처럼 props로 로직과 상태를 주입받게 되면 단위 테스트를 수행하기가 매우 편해집니다. 또한 단위 테스트를 위한 컴포넌트를 한 파일에서 관리할 수 있게 되므로 더욱 효율적인 코드 관리가 가능해집니다.

 

|____  Calendar.tsx: Calendar.component.tsx에서 정의한 단위 컴포넌트들을 Calendar.tsx에서 렌더링한다. (추후 컴포넌트의 로직과 함께, 통합 테스트로 검증한다.)

|__  /hooks: 서버와의 통신은 없어서 repository에 들어가기엔 조금 부족한 로직들을 정의한다.

|____ /useCalendar.tsx: Calendar 컴포넌트에서 사용하는 프론트엔드 로직을 정의한다.

|__  /interfaces: 컴포넌트와 관련된 interface들이 정의된다.

|__  /repository: 프론트엔드에서 데이터를 저장하거나 가져오고, 관리하는 로직이 정의된다. 간단하게 훅을 호출해서 처리하기 어려운 로직을 정의한다.

(Calendar 컴포넌트에서 사용되는 데이터 저장 로직이 없어서, 부득이 다른 컴포넌트의 로직을 가져왔습니다. 현재 리팩토링 중이라서 완벽하게 적용된 코드를 가져오기 어려웠던 점 양해부탁드립니다.)

|__  /service: 컴포넌트에서 실행되는 함수 또는 로직을 정의한다. React 생태계에 종속적인 훅들은 컴포넌트에서 주입해준다.

이렇게 컴포넌트의 각 기능들을 폴더로 구분하고 관심사 분리의 작업을 수행하고 나니, 코드 유지관리와 테스트가 수월해지고 있다는 것을 점점 느끼게 되었습니다. 물론 아직 각 폴더의 정확한 기능을 숙지하지 못한 상태라 리팩토링에 꽤 시간이 걸리고 있는 상황인데 이 프로젝트를 점차 개선해나가면서 더 효율적인 아키텍쳐 구조를 고민해보고 합니다.

(그리고 OOP에 대한 개념이 정말 필요하고 중요하구나 라는 부분도 느꼈습니다.)

 

개선해야할 점

여전히 프로젝트 단위의 아키텍쳐이다. (= MFA, Micro Frontend Architecture 가 필요하다.)

SARDIP WEB-GIS는 추후 기능이 더 추가될 수도 있고, 디자인이 변경될 수도 있는 등의 변경사항을 절대 예측할 수 없습니다. 제가 이 회사에 평생(?) 있게 된다면 프로젝트 관리야 문제가 없겠지만, 그럴 일은 없고 결국 다른 누군가가 와서 이를 받아 유지보수를 해야할 것인데 이 정도로 규모가 커진 프로젝트에서는 하나의 프로젝트에서 모든 코드를 관리하게 되면 그 작업이 너무 힘들 것이 눈에 뻔히 보였습니다.

 

백엔드의 경우 MSA(MicroService Architecture)를 적용하게 되면, 각각의 핵심 비즈니스 로직들이 모듈화가 되어있어서 그때 그때 필요한 기능들을 붙이거나 떼거나 하는 방식으로 프로젝트를 진행해 나갈 수가 있는데, 프론트엔드에서는 어떠한 방식으로 프로젝트 복잡도를 해소해나가고 있는지 매우 궁금했습니다.

 

내부 회의와 개인 공부들을 진행하며, MFA라는 방법론이 존재한다는 것을 알게 되었습니다. UI 컴포넌트들도 다른 프론트엔드 서버에서 구동하여 가져와 렌더링하는 방식으로 관심사를 분리하면 더욱 모듈화된 복잡도가 줄어드는 프로젝트가 가능하다는 것을 알게 되었습니다. 현재 공부중에 있는데, 다음 글에서는 프로젝트 리팩토링을 진행하며 적용하게 될 MFA 도입기와, 현재 프로젝트에 작성하는 프론트엔드 테스트코드와 관련된 내용 등을 더 심도있게 풀어보도록 하겠습니다.