프론트엔드 아키텍처의 의미와
느슨함의 가치
프론트엔드를 위한 느슨한 아키텍처 - 1
아키텍처란?
소프트웨어 아키텍처의 목표는 필요한 시스템을 만들고 유지보수하는 데 투입되는 인력을 최소화하는 데 있다.
- 로버트 C. 마틴, <클린 아키텍처>
소프트웨어 아키텍처라는 용어를 정의하는 것은 까다로운 일이다. 자칫하면 아무 것도 설명하지 못하는 공허한 단어가 될 수 있기 때문이다. 아키텍처에 대해 논하는 글들은 대체로 정의에 집착하기보다, 그것의 목적이나 유형, 구현 방법과 같은 핵심 문제들에 집중하곤 한다. 소프트웨어 아키텍처란 "그게 무엇이든 중요한 것에 관한" 개념이라는 랄프 존슨의 말을 빌린 마틴 파울러의 글이나 위에서 인용한 로버트 C. 마틴의 저서 <클린 아키텍처>가 대표적인 예다.
이 글을 비롯하여, 프론트엔드를 위한 느슨한 아키텍처 시리즈에서 집중할 문제는 바로 아키텍처가 소프트웨어 개발, 그 중에서도 프론트엔드 개발이라는 특수한 하위 분과에서 갖는 실용성이다. 아키텍처는 프론트엔드 개발 프로젝트를 어떻게 바꿀 수 있을까? 특정 아키텍처를 도입하면 프론트엔드 개발자의 하루 일과는 어떻게 바뀌고, 그 변화는 정말 유의미할까? 이러한 질문에 답하기 위해, 우리는 아키텍처를 다음과 같이 정의해볼 수 있다.
아키텍처는 개발자가 하중을 견디는 방식을 결정한다.
다시 두 가지 질문이 남는다. 첫째, 개발자에게 주어지는 하중이란 무엇인가? 둘째, 개발자가 이를 견디는 방식이란 무엇이고, 어떻게 달라질 수 있는가? 이 두 가지 질문에 간략히 답함으로써 앞으로의 논의에 필요할, 아키텍처라는 용어에 대한 최소한의 합의를 이루고자 한다.
하중
어떤 조직이든 개발자의 존재 이유는 서비스에 필요한 소프트웨어를 주어진 시간과 비용 내에서 만들어내는 것이다. '서비스에 필요한' 소프트웨어는 프로젝트가 진행됨에 따라 계속 변화한다. 기획이 확장 또는 수정되기도 하고, 사용자가 늘어 더 큰 트래픽을 감당할 필요가 생기기도 하기 때문이다. 개발자에게 주어지는 하중이란 이처럼 어떠한 소프트웨어를 구현해달라는 새로운 요청의 연속이다.
대부분의 경우 이 요청의 난이도는 점점 증가한다. '초기 시스템을 백지에서부터 만들어내는 게 더 어렵지 않겠는가'라 반문할 수도 있겠다. 그러나 하늘 아래 새로운 기획은 없고, 세상에는 이미 좋은 레퍼런스가 많다는 사실을 잊지 말자. 이를 참고하며 간단히 동작하는 소프트웨어를 어떻게든 작성하는 것은 대부분의 프로그래머가 할 수 있는 일이다.
하지만 기존의 시스템을 변경하는 것은 다른 문제이다. 시스템의 기능과 이를 구현한 코드는 시간이 갈 수록 복잡해지고, 관리 책임자 또한 많아진다. 여기에 코드를 적절히 수정, 추가하여 기능을 수정하거나 새로운 기능을 추가하는 일은 달리는 차의 부품을 교체하는 것만큼 까다롭다. 기존의 기능이 오작동하는 경우도 흔하고, 추가된 부분과 큰 상관이 없는 코드까지 건들게 되는 일도 비일비재하다. 길고 복잡한 역사가 쌓인 코드는 다음 변경에 대한
그 결과 소프트웨어에 대한 변경 요청, 즉 하중을 감당하기 위해 필요한 인력, 기간, 그리고 자원은 시간이 갈 수록 증가하게 된다. 최악의 경우 이 증가폭은 지수적인 형태를 띄는데, <클린 아키텍처>에서는 이 현상을 대표적인 개발 프로젝트의 실패 요인이라 말하기도 한다. 프로젝트의 성공을 위해서 개발자들은 하중을 효율적으로 견딜 방법을 고안해야 한다.
방식
하중을 견디기 위해 개발자는 어떤 방식을 취할 수 있을까? 가장 원시적인 방법은 매 요청마다 코드를 처음부터 다시 작성하는 것이다. 이전에 작성했던 코드는 깔끔히 잊어버리고, 누적된 모든 요청을 만족하는 소프트웨어를 백지부터 구축할 수 있다. 반대로 이전의 코드를 최대한 재사용하는 방식을 택하는 것도 가능한데, 이 경우에는 코드를 작성하기 보다는 기존의 코드를 읽고 재사용하는 작업이 추가로 필요하지만, 작성해야 할 코드의 양은 이전보다 감소할 수 있다.
선택한 방식에 따라 개발자 집단의 일과는 크게 달라진다. 첫번째 방식을 택한 개발자의 하루는 오직 요청을 이해하고 코드를 작성하는 데에만 쓰일 것이고, 두 번째 경우에는 기존 코드를 읽는 데 꽤 많은 시간을 보낼 것이다. 이처럼 극단적이지 않더라도, 실제로 팀(또는 자기 자신)의 결정에 따라 개발자가 보내게 되는 일과의 형태는 꽤나 다양하게 나뉜다. 아래는 개발자 집단이 경험할 수 있는 좀 더 현실적인 일과의 예시이다.
- 대부분의 시간을 기존 코드 읽기에 소모한다. 아주 오래전부터 쌓여 온 코드는 충분한 주의를 기울여야지만 무사히 수정할 수 있기 때문이다. 이후 짧은 시간 동안 코드를 변경하고, 다시 코드를 읽기 시작한다.
- 일단 요청에 따라 코드를 큰 고민 없이 작성하기 시작한다. 빠르게 작성을 마친 후에는 기존 시스템과의 충돌을 해결하고 코드를 정리하는 데 많은 시간을 할애한다.
- 요청을 만족하는 시스템의 구조를 적절한 용어와 다이어그램으로 먼저 표현한 후, 이에 맞춰 코드를 작성한다. 새로운 요청이 들어오면 다이어그램을 기반으로 어떤 변경이 필요할 지 판단하고 코드를 고친다.
- 요청이 들어올 때마다 개발자들끼리 대규모 회의를 열어 위의 세 방식 중 하나를 결정한 후 개발을 시작한다.
이러한 방식을 구성하는 개발자들의 모든 결정사항에 '아키텍처'라는 이름을 붙이도록 하자. 아키텍처는 개발자의 일과, 즉 어떤 개발자의 시간과 집중력이 어디에 얼마나 투자될 지를 결정한다. 매일 아침 작성하는 할 일 목록의 내용과 일일 스크럼의 논의 주제를 결정한다. 구성원들이 창의력을 발휘할 수 있는 순간과 관습을 따라야 하는 순간을 결정한다. 어떤 하중을 효율적으로 감당하고 어떤 하중에 취약한 지를 결정한다.
앞서 말했듯 하중은 시간에 따라 증가하므로, 오랜 시간이 지나도 큰 비용의 증가 없이 하중을 감당할 수 있게 하는 방식은 꽤나 실용적이다. 반대로 모든 코드를 처음부터 새로 작성하는 것과 같이, 소프트웨어의 규모가 조금만 커져도 적용하기 어려운 방식은 현명하지 못하다고 할 수 있겠다. 프로그래밍의 역사 동안 개발자에게는 많은 선택지가 제시되어왔다. 그러나 언제 어디서나 성립하는 '최고의 방식'이란 존재하지 않는 듯하다. 서비스의 종류, 자신이 맡은 역할, 팀의 마케팅 전략이나 여유 자원 등 다양한 요소에 따라 최적의 방식은 계속 변화한다.
프론트엔드 아키텍처의 특징
프론트엔드 프로그래밍이란 웹 페이지나 모바일 애플리케이션 개발에서, 사용자와 직접적으로 상호작용하는 인터페이스를 구축하는 분야를 지칭하는 용어이다. 그와 구분되는 분야로는 백엔드 프로그래밍이 있다. 많은 아키텍처는 프론트엔드와 백엔드의 구분이 뚜렷하지 않던 시절에 고안되었다. 따라서 초창기의 예시를 프론트엔드 분야에 그대로 적용하기에는 까다로운 점이 많다.
내가 지금까지 경험한 프론트엔드 프로그래밍은 다음과 같은 특징을 가진다. 첫째, 개발 단계에서 가장 말단에 위치한다. 둘째, 그런 이유로 다양한 이해 관계자와 협업하여 문제를 해결해야 한다. 셋째, 서비스가 커질수록 다양한 구동 환경에 대응할 필요가 있다.
말단
프론트엔드 개발자는 많은 경우 구현 단계에서 가장 마지막까지 코드를 적는 개발자이다. 사용자와 가장 가까이 위치한 프로그램을 작성하는 사람이기 때문이다. 프론트엔드 개발자가 작업을 마치기 위해서는 화면과 동작에 대한 완전한 기획, 여기서 동작부를 구현한 백엔드의 API, 그리고 화면에 관한 최종 디자인이 모두 준비되어 있어야 한다. 이 모든 요소가 프론트엔드 개발자의 프로젝트에서 통합되기 때문이다. 만약 세 요소중 일부가 준비되지 않았거나 흠결이 있다면, 프론트엔드의 산출물은 온전히 동작할 수 없다.
이 특징은 자연스럽게 다음 특징으로 이어진다.
다양한 이해 관계자
프론트엔드 개발자는 다양한 이해 관계자의 요구를 이해하고 검토할 필요가 있다. 물론 프론트엔드 개발자가 프로젝트 매니징을 한다거나, 팀 소통의 중심이 된다는 것은 아니다. 비즈니스 로직에 대해서는 기획자와 마케터, 백엔드 개발자가 훨씬 신중하게 고민할 것이고, 이를 효과적으로 담아낼 UI·UX에 관해서는 디자이너가 가장 전문적일 것이다. 그러나 모든 요소를 통합하는 작업에 대해서는 프론트엔드 개발자가 큰 책임을 지닌다. 백엔드의 API 설계와 디자이너의 프로토타입과 충돌하거나, 기획자가 요구한 최소 성능을 서버와 인터페이스의 성능이 만족할 수 없는 경우 이를 제일 먼저 포착할 수 있는 사람은 프론트엔드 개발자이다. 통합 과정에서의 오류가 코드베이스 내에 드러나기 때문이다. 이 역할을 성공적으로 수행하기 위해 프론트엔드 개발자는 자신의 통합 작업에 영향을 주는 작업들의 목표와 진행상황을 파악하고, 기술적인 관점에서 적절한 의견을 제시해야 한다.
다양한 이해 관계자와 협업한다는 것은 프론트엔드 개발자에게 또 다른 과제를 부과하기도 한다. 바로 각 조직의 작업 방식에 유연하게 대응할 수 있어야 한다는 과제이다. 기획, 디자인, 백엔드 개발, QA나 마케팅, CS 조직까지. 팀은 아주 많은 조직으로 구성되고, 이들의 작업 방식과 마감 기한, 오류의 비율은 계속 변화한다. 때로는 특정 조직에 문제가 생겨 결과물 전달이 늦어지기도 한다. 성공적인 프론트엔드 조직은 정해진 시간 내에 통합을 마치기 위해 조직별 다양한 상황에 대처할 수 있는 방법을 마련해야 한다. 모킹 서버를 두어 백엔드 조직에서의 작업 병목을 해결하거나, 피그마와 스케치북을 연동하여 디자인 시스템의 예기치 못한 변동을 안정적으로 관리하는 작업 등이 그 대표적인 예시이다.
다양한 구동 환경
프론트엔드 개발자가 대처해야 할 다양한 환경은 단지 팀 내에서 뿐만이 아니다. 소프트웨어의 구동 환경에 대해서도 유연성을 확보해야 한다. 물론 다른 개발 분야 역시 다양한 구동 환경을 경험할 일은 존재한다. 백엔드 서버만 해도 클라우드 서비스와 물리 서버 환경, 또는 쿠버네티스와 같은 플랫폼 도입 여부에 따라 배포와 구동 방식이 달라지곤 한다. 그러나 프론트엔드 개발의 진정한 까다로움은 프로그램이 언제나 사용자의 장치에서 구동된다는 점에서 온다. 프론트엔드 프로그램의 배포 환경은 구동 환경과 다르다. 프론트엔드 개발자는 자신이 소유하거나 대여한 배포 환경이 아니라, 사용자가 서비스에 접속할 수 있는 모든 환경에서 제대로 구동될 소프트웨어를 구현해야 한다.
사용자의 장치는 매우 다양하다. 기존에는 사용자의 브라우저나 윈도우 / macOS와 같은 운영체제의 다양성에 대응하는 것이 프론트엔드 분야의 주 관심사였다. 그러나 모바일 기기의 등장으로 다양성의 범위는 훨씬 넓어지게 되었다. 모바일 용 브라우저가 등장하고, 안드로이드와 iOS를 비롯한 새로운 운영체제가 추가되었다. 모바일 애플리케이션을 구현하는 방식 동안 네이티브 앱, 하이브리드 앱이나 웹뷰, 프로그레시브 웹 앱 등으로 다양하다. 뿐만 아니라, 보다 근본적으로 프론트엔드 개발자가 대응해야 할 사용자 인터페이스가 달라지기도 했다. 모바일 기기는 가로폭이 넓은 데스크톱과 다르게 세로 길이가 더 긴 화면을 제공하고, 상호작용 방식 또한 데스크톱에서 찾아보기 힘든 터치와 슬라이드에 기반하고 있기 때문이다.
앞서 시간이 지나면서 개발자의 하중이 점차 늘어난다고 했는데, 더 많은 환경에 대응하는 것은 프론트엔드 개발자에게 추가로 가해지는 하중이기도 하다. 요즘에는 데스크톱 웹 용 서비스를 모바일로 이식하거나, 반대로 모바일 앱을 서비스를 데스크톱 용으로 확장하는 경우를 심심치 않게 찾아볼 수 있다. 특정 환경에 최적화된 소프트웨어를 전혀 다른 환경으로 이식하는 것은 아주 까다로운 작업이다.
프론트엔드 개발자를 위한 아키텍처
지금까지 프론트엔드 개발의 세 가지 특징을 살펴보았다. 그렇다면 프론트엔드 개발자를 위해서는 어떤 아키텍처가 적합하다고 할 수 있을까? 이 질문에 대하여 어떤 아키텍처의 이름 하나를 대는 것은, 아키텍처의 왕도는 존재하지 않는다는 첫 단락에서의 설명과 정면으로 위배되는 것 같다. 그렇지만 프론트엔드 개발의 하중을 대체로 현명하게 감당할 수 있는 아키텍처들의 공통된 특징에 대해 고민해볼 수는 있다. 그리고 내가 내린 결론은 다음과 같다. 프론트엔드 아키텍처에서 가장 중요한 요소는 바로 느슨함이다!
느슨한 아키텍처
이 단락에서 이야기할 '느슨함'이란 어떤 요소 간의 결합도가 낮은 상태를 지칭한다. 느슨함은 프론트엔드 아키텍처가 지향해야 할 가장 중요한 가치이다. 적어도 내가 생각하기에는 그렇다.
느슨한 아키텍처를 적용한다는 것은 어떤 요소 간의 결합을 경계하는 것일까? 느슨함이 프론트엔드 프로젝트에 허용하는 가치는 무엇일까? 글의 남은 부분에서는 이 질문들을 간략히 다뤄보되, 그렇게 깊이 있게 서술하지는 않을 것이다. 대신 이후의 글에서 구체적인 방법과 예시를 더하여 느슨한 아키텍처가 지향하는 바와 그 실용성을 보다 자세히 설명하고자 한다.
두 개의 초점

출처: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
많은 아키텍처들은 위와 같은 형태의 다이어그램, 즉 여러 겹의 원이 층층히 겹쳐진 형태로 표현되곤 한다. 소프트웨어를 구조화함에 있어 레이어를 나누고, 그것을 중심으로부터 가까운 정도로 배치하는 것은 매우 유용하기 때문이다. 위의 다이어그램에서는 덜 변경되는 요소를 원의 안쪽에, 보다 자주 변경되는 세부사항을 원의 바깥쪽에 배치한 후 내부 요소가 외부 요소를 참조하지 못하게 함으로써 의존성의 흐름을 통제하는 방식을 제시하는데, 이는 클린 아키텍처의 핵심 원리이다.
프론트엔드 아키텍처 역시 이러한 다이어그램을 유용하게 사용할 수 있지만, 딱 한 가지 점에서 다르다. 바로 원이 두 개라는 것이다. 하나는 데이터(보통 기획자가 고안하여 백엔드 개발자가 구현한 후 프론트엔드에 전해지는)의 원이고, 다른 하나는 디자인(보통 기획을 바탕으로 디자이너가 작업한 후 프론트엔드로 전달되는)의 원이다. 이 두 원의 요소들은 프론트엔드 프로젝트에서 조립되기는 하지만, 하나의 구조 내에서 설명될 수는 없다. 느슨한 아키텍처가 수행해야 하는 첫 번째 해체 작업은 지금껏 하나로 여겨졌던 프론트엔드 아키텍처를, 데이터와 디자인이라는 두 개의 초점을 가진 두 개의 아키텍처, 두 개의 원으로 분리하는 것이다.
조립을 최대한 미루기
느슨한 아키텍처가 경계해야 하는 결합은 단지 이것뿐만이 아니다. 각 아키텍처의 내부에서도 결합은 최소한으로만 존재해야 한다. 이를 위해 택할 수 있는 전략은 바로 조립을 최대한 미루는 것이다.
간단한 예를 하나 살펴보자. 프론트엔드 개발자가 데이터를 다룰 때 사용하는 요소에는 어떤 것이 있을까? 기본적으로는 엔티티, 즉 비즈니스 로직을 표현하는 타입들이 있을 것이고, 실제 데이터를 가져오는 백엔드 서버로의 API 요청도 존재한다. 또 서버의 상태 또는 사용자의 동작에 대응하기 위한 상태와 상태 관리자도 존재하고, 데이터를 사용자에게 보여주고 입력을 받을 UI 컴포넌트도 필요하다. 이 요소 간의 유지하는 방법은 바로 조립 작업을 가능한 미루는 것이다. 아래와 같은 작업을 상상해보라.
- 비즈니스 로직에 기반한 엔티티와, 재사용 가능한 상태, API 요청 로직, UI 컴포넌트를 각각 선언한다.
- 엔티티와 상태를 연결하는 상태 관리자를 만들어 두 요소를 조립한다.
- 각 상태 관리자에, 필요한 API 요청 로직을 끼워 넣는다. 로직은 이미 결합된 엔티티와 상태가 요구하는 형태여야 한다.
- 그 결과물을 UI 컴포넌트와 결합한다.
- 마지막으로 UI 컴포넌트들을 서로 조립하여 페이지를 만들면, 작업이 마무리된다.
이처럼 작업의 초반부에서 요소들을 선언하고, 후반부에서 이를 조립하면 단계적으로 요소를 변경하거나 재사용하는데 많은 이점을 지니게 된다. 백엔드 서버의 구성이 바뀌면서, 몇몇 데이터를 백엔드 서버가 아니라 로컬 환경에 저장하게 되었다고 가정해보자. 첫 단계에서 엔티티와 백엔드 API 요청을 이미 조립해두었다면, 상태 관리자를 비롯한 모든 코드에 변경이 일어날 것이다. 그러나 위의 경우에서는 API 요청 로직을 로컬 저장소를 조회하는 코드로 변경하는 것만으로 변경에 대응할 수 있다. 백엔드 조직에 문제가 생겨 갑자기 모킹 서버를 도입해야 하는 경우에도 이러한 전략은 유용하다.
이 단락의 내용에 대해서는 다음 글이 될 느슨한 데이터 아키텍처에서 자세히 알아볼 것이다.
얽힘을 서술하는 어휘
느슨한 아키텍처는 이러한 방법으로 요소들의 결합도를 약화시키며, 이는 프론트엔드 개발에서의 유연성을 확보하는데 많은 도움을 준다. 그러나 언제까지 소프트웨어를 느슨하게 둘 수는 없는 법이다. 완성된 소프트웨어를 사용자에게 전달할 때에는, 내부가 이토록 느슨하고 쉽게 대체 가능한 요소들로 구성되어 있다고 상상하지 못할 만큼 완성도가 높아야 하기 때문이다.
이러한 요구에도 느슨한 아키텍처는 유용할 수 있다. 느슨한 구조는 얽힘, 즉 각 요소들이 어떻게 연결 - 재사용 - 조립되는 지를 알기 쉽게 만든다. 느슨히 묶인 매듭과 비슷하다. 언젠가 우리가 매듭을 단단히 묶어야 할 때, 즉 완전히 결합된 상태로 사용자에게 전달해야 할 시점에 우리가 할 일은 느슨하게 얽힌 구조를 들여다보며 어떤 끝을 잡아당겨야 매듭이 단단히 묶이는 지를 가려내는 것 뿐이다.
다시 말하자면, 느슨한 아키텍처가 지향하는 바는 단순히 요소를 잘게 쪼개는 것이 아니라 그 얽힘을 이해하기 쉽게 들어내보이는 것이라 정리할 수 있겠다. 얽힘을 서술하는 어휘를 제공하는 것이라 표현할 수도 있겠다. 아무런 규칙 없이 분리된 요소들은 결합도가 낮다 할 지라도 얽힘을 표현하지 못한다. 좋은 프론트엔드 아키텍처는 이 요소들을 잘 분류하고, 배치하고, 그 관계를 정확히 명시함으로써 독립적인 요소들을 단단한 프로덕트로 엮어낼 조작 지점이 어디인지 보여주어야 한다. 주로 NextJS와 같은 프레임워크로 모든 작업물을 감싸는 일(흔히 프론트엔드 개발의 전부라고 오해받곤 하는 일)이 그러한 지점이 되곤 한다.
나에게 남은 과제
여기까지 읽었으면, 느슨한 아키텍처란 실체는 존재하지 않고 그냥 좋은 말을 적당히 이어붙인 아키텍처라는 강렬한 인상을 떨쳐버리기 힘들 것이다. 이 글만으로는 느슨한 아키텍처가 실제 프로젝트에 어떻게 적용될 수 있을 지를 전혀 짐작할 수 없다. 그리고 이는 내가 의도한 바이기도 하다. 실천 수칙에 대해 먼저 이야기하는 것은 느슨한 아키텍처가 지향점이 아니라 구체적인 프로그래밍 패턴처럼 여겨지게 할 수도 있기 때문이다.
앞으로 이어질 데이터와 디자인 아키텍처에 관한 글은 구체적인 방법론과 예시를 들어 느슨한 아키텍처의 가능한 예시 하나를 설명하고자 한다. 부디 이 글들을 읽고 나면, 아키텍처에 관한 나의 생각이 여러분에게 영감을 주는 이야기로 보다 설득력있게 다가갈 수 있기를 바라본다.