[Flutter] Multi Package 사용하기

Noah·2024년 5월 6일
0

Flutter

목록 보기
5/11

Mutil Package란?

멀티 패키지는 하나의 프로젝트에서 쓰이는 코드를 여러 패키지로 나누어 관리하는 방법이다. 각 패키지는 관련된 기능을 담당하고, 프로젝트 내에서 서로 의존성이 있을 수 있다.

왜 Multi Package를 사용해야 하지?

프로젝트가 거대화되면서 모노리딕 시스템은 문제를 야기한다. 대표적으로 높은 결합도낮은 응집력을 예로 들 수 있다. 이를 해결하기 위해서 개발 조직은 시스템의 각 부분을 도메인 별로 분리해서 마이크로 서비스로 구성하기 시작한다. 이때 멀티 패키지를 사용하면 쉽게 공유할 수 있는 모듈식 코드를 만들 수 있다.

이때 쪼개진 각 서비스를 하나의 리포지토리에서 관리할지, 각자 다른 리포지토리에서 관리할지 고민하게 된다.
리포지토리를 관리하는 방법은 시스템의 각 모듈을 개별 리포지토리에서 관리할 것인지, 하나의 리포지토리에서 관리할 것인지에 따라서 달라진다. 이때 나눠서 관리하는 것을 Multirepo, 하나로 관리하는 것을 Monorepo라 정의한다.

각자의 장단점이 있지만 오늘은 Monorepo에 대해 살펴 보겠다.

Monorepo란?

  • 단일 저장소에 여러 프로젝트를 저장하는 소프트웨어 개발 전략
  • 코드를 패키지 별로 나누어 관리

장점

  • 지속적인 소스의 무결성 보장
    리포지토리는 항상 모든 서비스가 연동된 올바른 상태를 유지함
  • 통합된 버전 관리
    모든 서비스가 연동된 상태에서 손쉽게 하나의 버전으로 관리 가능
  • 코드의 공유와 재사용이 용이
    소스 단위의 연동이 이루어진 상태
  • 의존성 관리가 쉬움
    전체 서비스의 의존 관계가 한 리포지토리에서 확인 및 설정 가능
  • 원자 단위 변화
    변화가 여러 스텝이 아니라 한 리포지토리에서 한 스텝으로 이루어짐
  • 여러 프로젝트 팀 간의 협업이 쉬움
    하나의 리포지토리에서 함께 작업하며, 여러 서비스에 손쉽게 접근 가능
  • 유연한 팀 바운더리 설정과 코드 오너쉽을 가져갈 수 있음
    하나의 리포지토리, 하나의 서비스에 제한된 코드 오너쉽을 유지하지 않아도 됨
  • 통합 CI 및 테스트
    모든 소스가 연동된 상태. CI 및 테스트 구성이 손쉬움
  • 전체 코드가 트리 구조로 명확히 보임
  • 한 번의 코드 리뷰에 모든 변화가 요약

단점

  • 무분별한 의존성 연결 가능
    의존성 연결이 쉽기 때문에 오히려 과도한 의존 관계가 나타날 수 있음
  • 형상 관리 및 CI 속도 저하
    리포지토리의 크기가 크기 때문에, 리포지토리 훅을 기반으로 동작하는 도구들의 속도가 느려짐

예시

coupang_eats_monorepo
    -> app
      -> 고객용 앱A
      -> 드라이버용 앱B
      -> 관리자용 웹C
    -> eats_common_features
    -> eats_design_system
    -> eats_network
    -> eats_utils

이렇게 각각의 패키지로 분할하는 것은 협업/재사용성/유지보수에 매우 유용하다. 그러나 각각의 버전을 가지고 있는 많은 패키지를 관리하고 변경하는 것은 복잡하고, 추적 및 테스트가 어렵다.

Melos는 여러 패키지가 서로 완전히 독립적이면서도 하나의 저장소 내에서 함께 작동할 수 있도록 하여 이러한 문제를 해결하는 데 도움을 준다.

Multi Package로 프로젝트를 관리하기로 하였다면 이를 좀 더 쉽게 도와주는 도구 Melos가 있다.

Melos란?

  • Dart에서 여러 패키지를 관리하는 데 사용되는 CLI도구
  • Melos는 Flutter 커뮤니티에서 잘 알려진 팀, invertase 에 의해 개발되었다.
my_project
├── apps
│   ├── apps_1
│   └── apps_2
├── packages
│   ├── package_1
│   └── package_2
├── melos.yaml
├── pubspec.yaml
└── README.md

기능

  • 자동 버전 관리 및 변경 로그 생성.
  • pub.dev에 패키지를 자동으로 게시.
  • 로컬 패키지 연결 및 설치.
  • 패키지 전체에 걸쳐 동시 명령을 실행.
  • 로컬 패키지 및 해당 종속성 목록 조회.

Melos는 Dart 분석기가 패키지를 읽는데 사용하는 로컬 파일을 재정의하여 문제를 해결한다. melos.yaml 에 정의된 로컬 패키지가 존재하고 다른 로컬 패키지에 해당 패키지가 종속성으로 나열되어 있는 경우 버전 지정 여부에 관계없이 연결된다.

melos.yaml

name: my_project

packages:
  - apps/**
  - packages/**

melos bootstrap

  • 모든 패키지 종속성을 설치한다.(내부적으로 pub get을 사용하여)
  • pubspec.yaml 을 편집할 필요 없이 경로 종속성 재정의를 통해 모든 패키지를 로컬로 연결한다.
melos bootstrap
# or
melos bs

melos script (링크)

  • 스크립트는 melos 명령의 수명 주기 Hooks로 실행되거나 melos run으로 실행된다.
  • 스크립트는 쉘에서 실행된다. Windows에서 셸은 cmd.exe이고 다른 모든 플랫폼에서는 sh이다.
scripts:
  hello:
    name: hey
    description: Greet the world
    run: echo '$GREETING World'
    env:
      GREETING: 'Hey'
  • 스크립트에서 여러 명령이 실행되고 있고 명령이 실패한 후 더 이상 명령을 실행해서는 안 되는 경우 다음을 사용하여 명령을 연결한다. &&
scripts:
  prepare: melos bootstrap && melos run build

steps

  • 단일 스크립트 정의 내에서 여러 스크립트의 조합을 활성화한다. 아래 예에서 pre-commit 스크립트는 echo 'hello world' 를 호출하고 Melos 명령인 formatanalyze를 순차적으로 호출하도록 구성된다.
scripts:
  pre-commit:
    description: pre-commit git hook script
    steps:
      - echo 'hello world'
      - format --output none --set-exit-if-changed
      - analyze --fatal-infos

exec

  • melos exec를 통해 여러 패키지의 스크립트를 실행한다.
  • 이 옵션에는 여러 패키지에서 실행할 melos exec 명령이 포함되어야 한다.
  • 기본 옵션을 사용하는 경우 melos exec에서 명령을 지정하는 것이 가장 쉽다.
scripts:
  hello:
    exec: echo 'Hello $(dirname $PWD)'
  • run 명령에 대한 옵션을 제공해야 하는 경우 exec 에서 해당 옵션 명령을 지정한다. concurrency
scripts:
  hello:
    run: echo 'Hello $(dirname $PWD)'
    exec:
      concurrency: 1
concurrency
  • 동시에 명령을 실행할 패키지 수의 최대 값을 정의한다. 기본값은 5
failFast
  • exec 개별 패키지에서 스크립트가 실패하는 경우 빠르게 실패하고 추가 패키지에서 스크립트를 실행하지 않아야 하는지 여부. 기본값은 false
orderDependents
  • 패키지의 종속성 그래프를 기반으로 여러 패키지에서 스크립트 실행 순서를 지정해야 하는지 여부. 기본값은 false

env

  • 실행된 명령에 전달될 환경 변수의 맵.

packageFilters

  • exec 명령을 사용하면 여러 패키지에 대한 명령을 실행할 수 있다. 스크립트에서 사용되는 경우 섹션에서 필터 옵션을 선언할 수 있다.
  • 아래 스크립트 hello_flutterFlutter 패키지에서만 실행된다.
scripts:
  hello_flutter:
    exec: echo 'Hello $(dirname $PWD)'
    packageFilters:
      flutter: true

hooks

  • 특정 Melos 명령은 명령 실행 전후뿐만 아니라 명령 실행의 다른 흥미로운 지점에서도 스크립트 실행을 지원한다.
  • 후크를 지원하는 모든 명령은 최소한 prepost 후크를 지원합니다.
  • hooksmelos.yaml 파일의 명령 섹션에서 구성된다.
command:
  bootstrap:
    hooks:
      pre: echo `bootstrap command is running...`
      post: echo `bootstrap command is done`

analyze

  • 이 명령은 작업 영역의 모든 로컬 패키지를 분석한다.
melos analyze

clean

  • 이 명령은 현재 작업 공간과 임시 pub 및 생성된 Melos IDE 파일의 모든 패키지를 정리한다.
melos clean

파일에는 다음이 포함된다.

{packageRoot}/.packages
{packageRoot}/.flutter-plugins
{packageRoot}/.flutter-plugins-dependencies
{packageRoot}/.dart_tool/package_config.json
{packageRoot}/.dart_tool/package_config_subset
{packageRoot}/.dart_tool/version
{workspaceRoot}/.idea/runConfigurations/melos_*.xml

list

  • 로컬 패키지에 대한 정보를 나열한다.
melos list

version

  • 모든 패키지에 대한 변경 로그를 자동으로 버전화하고 생성한다.
melos version

[결론] Flutter에서의 Multi Package

그렇다면 Flutter에서 Multi Package를 어떻게 사용할까?
정답은 없지만 이상적이라고 생각되는 한 가지 예제를 가져왔다.

App

  • 애플리케이션을 의미한다.

  • 비슷한 기능과 데이터를 사용하는데, 앱이 여러개로 나뉘는 경우에 유용하다.

  • 모바일 카톡 PC 카톡과 같이, 같은 서비스지만 제공하는 플랫폼이 다를 때 사용할 수 있다.

  • 각 App에는 Config, Router(Navigator), 그리고 Page(Screen)가 들어간다.

Feature

  • Feature 패키지는 앱의 특정 기능을 구현하는 모듈들을 담고 있으며, 특정 기능을 완전히 독립적인 단위로 구성하여 개발 가능하다.

  • 각각의 Feature 패키지는 다시 독립적인 하위 패키지들로 구성될 수 있다.

  • 일반적으로 멀티패키지 아키텍처에서의 Feature는 클린 아키텍처와 유사한 방식을 취한다.

  • 각 Feature 패키지는 최소한의 의존성을 가지며, 외부로 노출되는 인터페이스를 통해 다른 패키지와 소통한다.

  • 패키지 간의 인터페이스를 정의함으로써 각 패키지는 독립적으로 개발, 배포, 유지보수할 수 있으며, 전체 시스템의 복잡도를 낮출 수 있다.

  • Feature가 다른 Feature와 강하게 결합되는 것을 피한다. 이를 통해 각 패키지는 서로 다른 비지니스 기능을 구현하면서 모듈성과 확장성을 유지할 수 있다.

  • 그렇기 때문에 일반적으로 데이터와 도메인 로직이 한 패키지 안에 있는 것이 더 좋다. 각 Feature가 자체 데이터를 갖고 필요한 모든 비지니스 로직을 캡슐화 할 수 있으며, 독립적으로 변경 및 확장될 수 있기 때문이다.

Shared model

  • 다른 패키지에서 사용될 수 있는 공통 모델이 있는 패키지다.

  • 각 패키지가 필요한 모델을 선택적으로 가져와 사용할 수 있으며, 패키지 간의 종속성을 최소화할 수 있다.

Common

  • 프로젝트에서 공통으로 사용되는 기능을 담당한다. 보통 Utility, Helper(validator, parser), constants와 같이 다른 모듈이나 라이브러리에서 자주 사용하는 공통적인 요소를 담당한다.

Core

  • 앱에서 전체적으로 사용되지만, 핵심이되는 부분들이다. 다른 모듈이나 라이브러리가 이를 참조하여 사용할 수 있도록 인터페이스를 제공한다. 보통 보안, 인증, 인프라 관리 등과 같이 프로젝트의 중요한 부분을 담당한다.

  • 인증 서비스와 같은 서비스, exception이나 error 처리를 위한 규칙 및 로직, Http 통신을 위한 Http Client와 같은 것들이 들어간다.

  • core와 common 패키지는 프로젝트에서 공통으로 사용된다는 점에서 비슷해 보이지만 다르다. common과 core의 구분이 어렵다면, 기능을 제공하느냐, 사용되느냐를 개념적으로 구분하면 편하다. 왜냐하면 주체가 되어서 기능을 제공한다는 것이 핵심 기능이라는 근거가 될수 있기 때문이다.

Design System

  • Theme, Components, Typography 같은 디자인 시스템에 해당하는 것들이 있는 패키지다.

결국 이 모든 것은 협업, 재사용성, 테스트를 위한 것이라 생각한다. 완벽한 정답은 없으며 각자의 상황과 성향에 따라 최선의 방법을 찾아가야 한다.

참조

Multi Package 프로젝트

melos 프로젝트

참조 블로그

profile
Flutter Specialist

0개의 댓글