Flutter 클린 아키텍처의 디렉토리 구성에 대한 고민

건전한건전지·2024년 1월 2일

고민의 시작

프로젝트를 시작하기 전 늘 그렇듯 개발하기 전 개발자들끼리 개발환경, 네이밍 등 협업에 관한 주제로 이야기 하는 시간을 가졌습니다. 같이 작업할 개발자분께 앱 소프트웨어 아키텍처에 관해 이야기를 나누었고 별다른 충돌 없이 'MVVM + 클린 아키텍처'로 의견을 맞추었습니다.
개념적인 구조는 정했고 남은 건 디렉토리 구조에 대한 내용인데...

디렉토리 구조

일반적인 구조

레이어별 폴더 구조(folder by layer)

간단한 서비스를 만들거나 개발을 처음 공부할 때 많이 보게되는 구조입니다.

data
	-- data_sources
    	-- feature1
        -- feature2
    -- models
    	-- feature1
        -- feature2
    -- repositories
    	-- feature1
        -- feature2
domain
	-- entities
    	-- feature1
        -- feature2
    -- repositories
    	-- feature1
        -- feature2
    -- usecases
    	-- feature1
        -- feature2
presentation
	-- pages(or screens)
    	-- feature1
        -- feature2
    -- widgets
    	-- feature1
        -- feature2
    -- viewmodels
    	-- feature1
        -- feature2

한눈에 알 수 있듯이 말그대로 역할(레이어)에 따라 폴더를 나누는 구조입니다.
이 구조는
구조를 파악하기 쉽고, 특정 역할(예를들어 usecase)를 찾고자 할 때 손쉽게 찾을 수 있는 장점이 있지만,
기능이 많아짐에 따라 하나의 폴더에 들어가는 파일의 양이 많아지게 되고, 하나의 기능을 추가하거나 삭제하려면 모든 폴더를 수정해야하는 단점이 있습니다.

기능별 폴더 구조(folder by feature)

위 언급한 레이어별 폴더 구조의 단점을 개선하기 위해 고안된 구조입니다.

feature1
  data
      -- data_sources
      -- models
      -- repositories
  domain
      -- entities
      -- repositories
      -- usecases
  presentation
      -- pages(or screens)
      -- widgets
      -- viewmodels
feature2
  data
      -- data_sources
      -- models
      -- repositories
  domain
      -- entities
      -- repositories
      -- usecases
  presentation
      -- pages(or screens)
      -- widgets
      -- viewmodels

각각의 기능을 담당하는 폴더를 만들고 내부에 클린 아키텍처 구조를 구현한 구조입니다.
위에서 언급한 레이어별 폴더 구조의 단점을 보완했고, 응집성 증가, 추후 서비스 분리 시 이점 등의 장점이 있습니다. 단점은 기능의 범위와 구분 기준의 모호함이 있습니다.

개발자들 사이에서는 대부분의 상황에서 기능별 폴더 구조가 더 적합하다는 의견이 많습니다.

기능별 폴더로 결정?

기능별 폴더 구조로 결정하고 개발하던 중 문득 떠오른 생각이 있었습니다.

테마 모드 변경같은 단순한 기능은 어떻게 해야할까?

테마 모드 같은 경우 단순히 스위치 버튼하나로 구현되는 경우가 많은데 그 버튼 하나를 위해 presentation폴더를 만들고 widgets폴더, 크게는 클린 아키텍처 구조를 만들어야하는가입니다.
테마 모드 변경을 하나의 기능으로 한다면 대략적인 폴더 구조는

ThemeMode
  data
      -- data_sources // 사용자 테마값 캐시
      -- models // 테마 모드 저장 모델
      -- repositories
  domain
      -- entities // 테마 모드 엔티티
      -- repositories
      -- usecases // 테마 토글 유즈케이스
  presentation
      -- pages(or screens) // 사용 X
      -- widgets // 토글 버튼
      -- viewmodels // 현재 테마 모드 뷰모델

로 구성될 것입니다.
여기서 앞서 말한 기능의 범위와 구분 기준의 모호함이 느껴졌습니다.
하나의 버튼을 위해 클린 아키텍처 폴더를 적용하지 않고, 테마 변경을 앱 세팅의 큰 기능으로 묶는다면 이 기능에는 어느 범위까지의 기능이 들어가야하는가? A 기능은 앱 세팅의 기능에 연관이 있을까? 등 꼬리의 꼬리를 무는 생각이 계속 떠올랐습니다.

또한 클린 아키텍처에 관해 검색하던 중 기능 != 페이지에 대한 포스트를 읽어 보았습니다. 한 페이지 안에는 많은 기능이 들어갈 수 있고, 아무 기능이 들어가지 않는 정적인 페이지도 있을텐데 어떻게 기능을 나눌 것인가?에 대한 내용이었습니다. 어떤 글에서는 shared, common 등을 공유 기능을 만들어 해결한다했지만, 만족스러운 해결책은 아니었습니다.

클린 아키텍처는 '개념'

클린 아키텍처는 아키텍처 개념이지 폴더 구조니, domain-data-presentation이니 명확한 구현에 대한 내용이 아니다. 클린 아키텍처에서 말하는 의존성의 방향, 관심사의 분리를 적용했다면 단순한 폴더 구조라도 클린 아키텍처라 할 수 있다.

어느 개발자분의 포스트를 요약한 내용입니다. 포스트를 읽고 클린 아키텍처를 적용할 구조에 대해 다시 생각하게 되었습니다.

'기능과 페이지는 별개. 그러나 폴더 구조는 기능별 폴더 구조'

presentation 레이어를 기능에서 분리하자!

기능별 + 레이어별 폴더 구조

features
	-- feature1
    	data
            -- ..
        domain
            -- ..
        presentation
            -- ..
    -- feature2
        data
            -- ..
        domain
            -- ..
        presentation
            -- ..
presentation
	-- pages(or screens)
    -- widgets
    -- viewmodels

presentation 레이어를 분리하면서 클린 아키텍처의 개념은 적용한 기능별 구조를 적용한 폴더 구조를 만들었습니다.
앞서 고민했던 정적 페이지, 여러 기능을 사용하는 페이지 등에 대한 고민을 해결하였습니다.

마치며

여러 개발자와 협업을 위해서라면 널리 알려진 구조도 좋다고 생각하지만, 꼭 정답은 없다는 생각과 새로운 접근법을 체험하고 싶어 동료 개발자와 협의하여 본 구조를 적용하기로 했습니다. 개발이 어느정도 진행된 현 시점에서 크게 문제점이나 애로사항 등 없이 적용 중에 있습니다!

이 글이 누군가에게 도움이 되었으면 좋겠습니다! :)

profile
Flutter 공부를 위한 블로그입니다.

0개의 댓글