ABOUT ME

-

Today
-
Total
-
  • React 프로젝트 구조 설계를 넘어서 프론트엔드 아키텍처 설계에 대한 고찰
    Study/Frontend 2023. 5. 19. 17:52
    반응형

     

    React 폴더 구조를 어떻게 구성할까?
    비즈니스 로직은 어떻게 분리할까?

     

    React와 같은 라이브러리로 신규 서비스를 개발하게 되면 프론트엔드 팀에서 가장 첫 번째로 고민해야 할 요소는 프로젝트의 폴더 구조라고 생각한다. Next.js와 같은 프레임워크로 개발을 하게 되면 폴더 구조가 어느 정도는 정해져있기 때문에 고민해야 할 부분이 비교적 적다. 하지만 React의 경우에는 정해진 폴더 구조는 없고 일반적인 접근 방식만을 제안하고 있다. 따라서 개발자에 따라서 프로젝트 설계 방법이 천차만별일 것이고 설계에 정답은 없다.

    1년 차때는 프론트엔드 프로젝트 구조 설계에 대해 깊은 고민을 하지 않고 내가 자주 쓰고 익숙한 패턴을 그대로 가져다가 새로운 서비스에 적용하곤 했다. 하지만 10년 차 동료 개발자가 프론트엔드 구조 설계에 있어서 충분히 고민하고 우리 비즈니스에 적합한 구조로 설계하는 것을 보게 됐다. 이를 통해 프론트엔드 아키텍처를 설계할 때 좋은 설계가 어떤 것인지 조금은 알게 됐다.

     

    이번 글에서는 React 프로젝트 구조 설계에 DDD(도메인 주도 설계)를 기반으로 한 Structure based on Feature를 사용해보며 느꼈던 점과 프론트엔드에서 좋은 아키텍처 설계란 무엇일까에 대한 고찰을 작성해보고자 한다.

     

    DDD(Domain Driven Design)란?

    💡 소프트웨어의 본질은 해당 소프트웨어 사용자를 위해 도메인에 관련된 문제를 해결하는 능력에 있다.

     

    DDD(Domain Driven Design)란 도메인 주도 설계 라고 하며, 말 그대로 도메인을 중심으로 설계하는 방법이다.

     

    https://vueschool.io/articles/vuejs-tutorials/domain-driven-design-in-nuxt-apps/

     

    여기서 도메인이란 소프트웨어로 해결하고자 하는 문제 영역(problem domain)을 의미한다. 예를 들어 온라인 쇼핑몰이라는 큰 서비스에서 결제, 주문, 배송 등과 같이 전체 서비스를 분리한 각각의 비즈니스 영역을 의미한다. 그리고 이렇게 분리한 도메인을 중심으로 설계하는 것을 도메인 주도 설계라고 한다.

    DDD의 주요 설계 원칙은 Loose Coupling(느슨한 결합)과 High Cohesion(높은 응집)이다. 도메인들 간에는 느슨한 결합을 하고 도메인 내에서는 높은 응집을 해야한다. 이를 위해 도메인을 잘 분리해야 하는 것이 필수적이다.

     

    DDD와 Hexagonal Architecture

    💡 The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation.

     

    Hexagonal Architecture의 핵심은 관심사의 분리를 통해 유연한 소프트웨어를 만드는 것이라고 할 수 있다. DDD의 원칙에 헥사고날 아키텍처를 사용한다면 도메인을 중심으로 설계하면서 계층간의 분리로 인해 시스템의 확장성을 높일 수 있다.

     

    https://www.youtube.com/watch?v=FeDBlSBPUz8

     

    헥사고날 아키텍처에서 의존성은 바깥에서 안으로 단방향 의존성을 가지며 어댑터를 통해서만 인프라 영역에 접근할 수 있게 된다. 이와 같이 어댑터 요소가 생김으로써 애플리케이션의 외부 인터페이스를 비교적 자유롭게 교체할 수 있는 유연함을 가지게 된다.

    1. UI (Presentaion) Layer : 애플리케이션과의 커뮤니케이션을 담당하는 계층이다. 쉽게 말해 일반적인 UI 컴포넌트라고 보면 된다. 예로 React UI 컴포넌트들의 구성이라고 할 수 있다.
    2. Adapter Layer: Redux가 이 계층에 해당한다. 어댑터는 포트와 애플리케이션 코어 간의 요청과 응답을 변환하는 역할을 한다. 예를 들어 Redux를 활용해서 UI에서 사용할 state와 action을 정의한다고 볼 수 있다.
    3. Infrastructure Layer: 레이어 간의 커뮤니케이션을 책임진다. 예를 들어 서버와 통신하는 구현체를 인프라 레이어에 두게 되면 도메인이나 애플리케이션에 의존하는 구조를 만들 수 있다. 그리고 예를 들어 data fetching 기술을 fetch API를 사용하다가 이것을 Axios 라이브러리로 변경할 수 있는 작업이 용이해지고 안쪽 레이어에 영향을 최소화시킬 수 있다.
    4. Application Layer: 비즈니스 로직이 들어있는 Use Cases들의 집합이다. 예를 들어 온라인 쇼핑몰에서 ‘카트에 추가하기’ 라는 유스 케이스에서 발생하는 액션들을 구현한다.
    5. Domain Layer: 도메인과 관련된 객체들의 집합이다. 도메인 레이어의 핵심은 외부로부터 독립적이라는 것이다. 예를 들어 온라인 쇼핑몰의 ‘카트에 추가하기’ 기능에서 유저 스스로 카트 추가 버튼을 눌러서 아이템이 추가됐는지 또는 프로모션 코드를 통해 자동으로 아이템이 카트에 추가됐는지는 도메인 레이어에서 신경 쓰지 않는다. 도메인 레이어에서는 추가할 아이템을 받아서 카트 아이템을 업데이트해주기만 하면 된다.

     

    React 프로젝트 폴더 구조에 DDD를 적용해보자

    우리 팀에서 사용한 프로젝트 폴더 구조는 DDD를 기반으로 한 ‘Structure based on Feature’이다. 이 폴더 구조는 동료 개발자와 함께 이 링크를 참조해서 설계를 진행했다. Structure based on Feature 구조에서 키포인트는 도메인 별로 폴더를 분리해서 도메인 내의 응집성을 높이고 서로 다른 도메인 간의 의존도를 낮추는 것이다.

    이를 적용한 React 프로젝트 폴더 구조는 아래와 같다.

    src/
    |-- assets/
    |-- components/
    |-- config/
    |-- constants/
    |-- features/
    |   |-- user/
    |   |   |-- api/
    |   |   |-- assets/
    |   |   |-- data/
    |   |   |-- hooks/
    |   |   |-- pages/
    |   |   |-- routes/
    |   |   |-- utils/
    |   |   |-- views/
    |-- hooks/
    |-- layout/
    |-- pages/
    |-- routes/
    |-- styles/
    |-- utils/
    |-- App.tsx
    |-- index.html
    
    • assets: 공통으로 사용하는 이미지, 폰트 등
    • components: 디자인 시스템에 기반한 공통으로 재사용하는 컴포넌트
    • features: 도메인별 집합 폴더
      • api: 도메인에서 사용하는 api
      • assets: 도메인에서 사용하는 이미지, 비디오 등
      • data: 도메인에서 사용하는 swr 데이터 (상태 관리 도구에 따라 store, contexts 등 다양한 명칭 가능)
      • hooks: 도메인에서 사용하는 react hook
      • pages: 도메인 routes path와 맵핑되는 페이지
      • routes: 도메인별 routes
      • utils: 도메인에서 사용하는 유틸리티 함수
      • views: 도메인 페이지에 사용되는 컴포넌트
    • hooks: 공통으로 사용하는 react hook
    • layout: 장치별(PC/Mobile) 레이아웃 컴포넌트
    • pages: 정적 콘텐츠 페이지들 집합 폴더
    • routes: 최상위 및 하위 도메인 경로 맵핑
    • styles: 전역 스타일
    • utils: 공통으로 사용하는 유틸리티 함수 및 객체

     

    서비스를 운영해 보며 느낀 DDD기반 Structure based on Feature 구조의 장점

    1. 유지보수 및 확장에 용이하다

    도메인 별로 유지보수 작업이 용이하다. 현재 프로젝트에서 도메인 별로 R&R을 했기 때문에 수정사항이 생겨도 작업하기 수월하다. 또한, 기존 작업자가 공백이 생겨 다른 작업자가 해당 도메인을 맡게 됐을 때도 그 도메인에 대한 이해만 한다면 유지보수에 용이하다.

    그리고 도메인 간에 결합이 낮기 때문에 여러 개발자들이 동시에 서로 다른 기능을 개발할 때 각자의 개발 영역이 다른 개발 영역에 미치는 영향이 낮다. 현재 운영 중인 프로젝트의 경우 동시다발적으로 신규 기능이 추가되거나 기존 기능 수정사항 요청이 많은 상황이다. 따라서 각자의 맡은 도메인 영역이 다른 도메인에 주는 영향을 최소화하며 사이드이펙트를 줄일 수 있다.

    그리고 추후 특정 도메인을 별도로 분리해서 배포하고자 하는 니즈가 있는 것으로 파악되기 때문에 DDD를 적용한 프로젝트는 확장성 측면에서도 좋다.

     

    2. 협업에 있어서 커뮤니케이션이 원활하다

    협업에는 항상 커뮤니케이션이 따라오는데 서로 같은 용어를 사용하더라도 다르게 이해할 수 있다. 예를 들어 온라인 쇼핑몰 비즈니스에서 Product라는 용어가 있다고 가정하자. Order 도메인을 다루는 팀은 Product를 바라볼 때 가격이나 재고 상품의 옵션을 볼 수 있고, Customer Support 도메인을 다루는 팀은 제품의 배송기간이나 평점을 보게 된다. 이처럼 같은 용어에 대해 서로 다른 관점으로 바라보게 되면 커뮤니케이션에 혼동이 올 수 있다. 하지만 DDD의 핵심 중 하나인 유비쿼터스 언어를 활용하게 되면 커뮤니케이션의 어려움을 낮출 수 있다.

    유비쿼터스 언어란 특정 도메인에서 특정 용어가 해당 도메인에서의 의도를 명확히 반영하고 핵심 개념을 잘 전달할 수 있는 언어이다. 그리고 모든 구성원들이 같은 용어에 대해 같은 것을 바라보는 것이 핵심이다. 용어가 정의될 때마다 용어 사전에 기록하고 명확하게 정의함으로써 모든 구성원이 공유해야 한다.

    이처럼 도메인에서 사용하는 용어를 코드에 반영함으로써 개발자에게 코드의 의미를 해석해야 하는 부담을 줄여주고 코드의 가독성을 높이고 이해하는 시간을 절약할 수 있다. 특히 업계 특성상 일반적이지 않은 용어들이 많은데 이러한 용어들을 명확히 정의하고 코드에 사용하면 개발자가 비즈니스 이해도를 높이는 동시에 타직군과의 커뮤니케이션도 원활하게 진행될 수 있다.

     

    서비스를 운영해 보며 느낀 아쉬운 점

    1. 유비쿼터스 언어 사전을 만들지 못했다

    프론트엔드 팀에서는 비즈니스 이해도를 기반으로 도메인을 분리하고 유비쿼터스 언어를 코드에 반영했다. 하지만 비즈니스라는 것이 중간에 바뀌지 말란 법이 없기 때문에, 특정 도메인에서 사용했던 용어가 개념은 같지만 용어가 바뀐 경우도 발생했다. 이런 경우 코드에 사용한 유비쿼터스 언어를 모두 교체해줘야 하는데 시간에 쫓겨 그렇지 못한 상황이 생겼다.

    그리고 서비스들이 추가되면서 유비쿼터스 언어들이 많이 늘어났는데 실무자들이 그것에 대해 정확히 인지를 하지 못하고 기획, 서버, 프런트 각각 다른 용어를 사용하면서 커뮤니케이션에 혼선이 생기기도 했다. 유비쿼터스 언어 사전을 만들어서 프로젝트를 진행했다면 좋았겠다는 아쉬움이 남는다.

     

    2. 특정 도메인에 종속된 컴포넌트를 다른 도메인에서도 사용했다

    예를 들어 user 도메인에서 사용하던 ‘스팸 메일함 확인’이라는 컴포넌트가 있었다. 그런데 이 컴포넌트가 다른 도메인에서도 동일하게 사용되어야 하는 케이스가 발생했다. DDD에 따르면 이런 경우는 공통 컴포넌트 쪽으로 해당 컴포넌트를 옮겼어야 했는데, 이를 지키지 못하고 user 도메인에서 export 해서 사용한 부분이 있다. 해당 부분은 추후 리팩토링을 해야겠다.

     

    또 다른 프론트엔드 아키텍처 설계 방법론, FSD

    FSD는 DDD와 견주어도 괜찮을 법 한 프론트엔드 아키텍처 설계 방법론이라고 생각한다. 이 방법론은 동료 개발자가 알려줬는데 처음에는 현재 프로젝트 구조와 비교해서 장점을 캐치하지 못했다. 하지만 문서를 전반적으로 읽어보니 비즈니스 요구 사항들이 빠르게 변화하고 추가되는 우리 회사 비즈니스에 적합한 방법론이라는 생각이 들었다.

    💡 Feature-Sliced Design (FSD)는 프론트엔드 애플리케이션을 스캐폴딩하기 위한 아키텍처 방법론이다. 이 방법론의 주요 목적은 끊임없이 변화하는 비즈니스 요구 사항에 직면하여 프로젝트를 더 이해하기 쉽고 체계적으로 만드는 것이다.

    https://feature-sliced.design/

     

    Welcome | Feature-Sliced Design

    Architectural methodology for frontend projects

    feature-sliced.design

     

    FSD의 특징은 아래와 같다.

    1. Explicit business logic: 도메인 범위 덕분에 쉽게 찾을 수 있는 아키텍처
    2. Adaptability: 새로운 요구 사항에 따라 아키텍처 구성 요소를 유연하게 교체하고 추가할 수 있다.
    3. Tech debt & Refactoring: 사이드이펙트 없이 각각의 모듈을 독립적으로 수정할 수 있다.
    4. Explicit code reuse: DRY(Do not Repeat Yourself)와 로컬 맞춤형에서 밸런스를 유지할 수 있다.

    FSD에서 프로젝트는 다음으로 구성된다. layers가 있고, 각 layer는 slices로 구성되고, 각 slice는 segments로 구성된다. 자세한 사항은 FSD 문서를 참조하면 좋고, 현재 우리 비즈니스에 어떻게 적용해 볼지 고민해 봤다.

    React 프로젝트 폴더 구조 설계에 FSD를 적용해 봤다. 문서에 있는 예제들과는 살짝 다르게 각 레이어에서 최대한 도메인별로 분리를 했고, 레이어의 도메인마다 슬라이스를 생성하는 방향으로 설계해 봤다.

    src/
    |-- shared/
    |   |-- ui/
    |   |-- lib/
    |   |   |-- utils/
    |   |   |-- hooks/
    |   |-- config/
    |   |-- constants/
    |   |-- types/
    |-- entities/
    |   |-- user/
    |   |   |-- ui/
    |   |   |-- model/
    |   |   |-- lib/
    |   |   |-- api/
    |-- features/
    |   |-- user/
    |   |   |-- signup/
    |   |   |-- signin/
    |-- widgets/
    |   |-- user/
    |   |   |-- ui/
    |   |   |   |-- profile/
    |   |   |-- lib/
    |-- pages/
    |   |-- user/
    |   |   |-- signup/
    |-- app/
    |   |-- styles/
    |   |-- routes/
    |   |-- assets/
    |-- App.tsx
    |-- index.html
    

    이게 FSD의 정석은 아닐 수 있지만 비즈니스 요구 사항에 따라 체계적이고 깔끔하게 만들 수 있는 아키텍처 중 하나라고 생각이 든다. 새로운 프로젝트를 진행하거나 기존 프로젝트를 리팩터링 할 때 이 방법론을 사용해 보고자 한다. 사용해보고 이 방법론에 대해서는 또 어떤 것을 느꼈는지 언젠가 글을 남겨보려고 한다.

     

    그래서 좋은 설계란 무엇일까?

    아키텍처 설계 방법론은 굉장히 다양하고, 좋은 설계를 위한 원칙도 굉장히 많다.

    내가 생각하는 좋은 설계란 프로그램을 유지보수하기 쉬운 설계라고 생각한다. 유지보수하기 쉽다는 것은 다양한 비즈니스 요구 사항에 맞춰서 확장 가능하고 유연하게 설계가 되어있다는 것이다. 그리고 이렇게 유지보수 하기 쉬운 설계는 구성원들 간의 커뮤니케이션을 도와주는 역할도 한다. 도메인에 대한 이해를 기반으로 좋은 설계를 하게 되면 서로 커뮤니케이션이 쉬워지고 끊임없이 변화하는 비즈니스에 따라 개발도 수월해질 것이다.

    결국 좋은 설계를 위한 방법론들 중에서 정답은 없다고 생각한다. 어떤 설계 방법론이 옳은지 따지기 보다 개발을 용이하게 할 수 있도록, 비즈니스의 복잡도로 인한 개발에 드는 비용을 줄일 수 있도록 하는 것이 좋은 설계라고 생각한다. 설계는 코드를 작성하기 전에 팀원들과 함께 고민해서 큰 틀을 잡아두고, 지속적인 리팩토링을 통해 좋은 설계로 나아가면 될 것이다.

     

     

     


    아래 문서를 참고하며 글을 작성했으며 보기를 꼭 추천드린다.

     

     

    반응형

    댓글