멀티 패키지는 하나의 프로젝트에서 쓰이는 코드를 여러 패키지로 나누어 관리하는 방법이다. 각 패키지는 관련된 기능을 담당하고, 프로젝트 내에서 서로 의존성이 있을 수 있다.
프로젝트가 거대화되면서 모노리딕 시스템은 문제를 야기한다. 대표적으로 높은 결합도와 낮은 응집력을 예로 들 수 있다. 이를 해결하기 위해서 개발 조직은 시스템의 각 부분을 도메인 별로 분리해서 마이크로 서비스로 구성하기 시작한다. 이때 멀티 패키지를 사용하면 쉽게 공유할 수 있는 모듈식 코드를 만들 수 있다.
이때 쪼개진 각 서비스를 하나의 리포지토리에서 관리할지, 각자 다른 리포지토리에서 관리할지 고민하게 된다.
리포지토리를 관리하는 방법은 시스템의 각 모듈을 개별 리포지토리에서 관리할 것인지, 하나의 리포지토리에서 관리할 것인지에 따라서 달라진다. 이때 나눠서 관리하는 것을 Multirepo, 하나로 관리하는 것을 Monorepo라 정의한다.
각자의 장단점이 있지만 오늘은 Monorepo에 대해 살펴 보겠다.

coupang_eats_monorepo
-> app
-> 고객용 앱A
-> 드라이버용 앱B
-> 관리자용 웹C
-> eats_common_features
-> eats_design_system
-> eats_network
-> eats_utils
이렇게 각각의 패키지로 분할하는 것은 협업/재사용성/유지보수에 매우 유용하다. 그러나 각각의 버전을 가지고 있는 많은 패키지를 관리하고 변경하는 것은 복잡하고, 추적 및 테스트가 어렵다.
Melos는 여러 패키지가 서로 완전히 독립적이면서도 하나의 저장소 내에서 함께 작동할 수 있도록 하여 이러한 문제를 해결하는 데 도움을 준다.
Multi Package로 프로젝트를 관리하기로 하였다면 이를 좀 더 쉽게 도와주는 도구 Melos가 있다.
my_project
├── apps
│ ├── apps_1
│ └── apps_2
├── packages
│ ├── package_1
│ └── package_2
├── melos.yaml
├── pubspec.yaml
└── README.md
Melos는 Dart 분석기가 패키지를 읽는데 사용하는 로컬 파일을 재정의하여 문제를 해결한다. melos.yaml 에 정의된 로컬 패키지가 존재하고 다른 로컬 패키지에 해당 패키지가 종속성으로 나열되어 있는 경우 버전 지정 여부에 관계없이 연결된다.
name: my_project
packages:
- apps/**
- packages/**
pub get을 사용하여) melos bootstrap
# or
melos bs
melos run으로 실행된다.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
pre-commit 스크립트는 echo 'hello world' 를 호출하고 Melos 명령인 format 및 analyze를 순차적으로 호출하도록 구성된다.scripts:
pre-commit:
description: pre-commit git hook script
steps:
- echo 'hello world'
- format --output none --set-exit-if-changed
- analyze --fatal-infos
melos exec를 통해 여러 패키지의 스크립트를 실행한다.melos exec 명령이 포함되어야 한다.melos exec에서 명령을 지정하는 것이 가장 쉽다.scripts:
hello:
exec: echo 'Hello $(dirname $PWD)'
run 명령에 대한 옵션을 제공해야 하는 경우 exec 에서 해당 옵션 명령을 지정한다. concurrencyscripts:
hello:
run: echo 'Hello $(dirname $PWD)'
exec:
concurrency: 1
5exec 개별 패키지에서 스크립트가 실패하는 경우 빠르게 실패하고 추가 패키지에서 스크립트를 실행하지 않아야 하는지 여부. 기본값은 falsefalseexec 명령을 사용하면 여러 패키지에 대한 명령을 실행할 수 있다. 스크립트에서 사용되는 경우 섹션에서 필터 옵션을 선언할 수 있다.hello_flutter는 Flutter 패키지에서만 실행된다.scripts:
hello_flutter:
exec: echo 'Hello $(dirname $PWD)'
packageFilters:
flutter: true
pre 및 post 후크를 지원합니다.hooks 는 melos.yaml 파일의 명령 섹션에서 구성된다.command:
bootstrap:
hooks:
pre: echo `bootstrap command is running...`
post: echo `bootstrap command is done`
melos analyze
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
melos list
melos version
그렇다면 Flutter에서 Multi Package를 어떻게 사용할까?
정답은 없지만 이상적이라고 생각되는 한 가지 예제를 가져왔다.

애플리케이션을 의미한다.
비슷한 기능과 데이터를 사용하는데, 앱이 여러개로 나뉘는 경우에 유용하다.
모바일 카톡 PC 카톡과 같이, 같은 서비스지만 제공하는 플랫폼이 다를 때 사용할 수 있다.
각 App에는 Config, Router(Navigator), 그리고 Page(Screen)가 들어간다.
Feature 패키지는 앱의 특정 기능을 구현하는 모듈들을 담고 있으며, 특정 기능을 완전히 독립적인 단위로 구성하여 개발 가능하다.
각각의 Feature 패키지는 다시 독립적인 하위 패키지들로 구성될 수 있다.
일반적으로 멀티패키지 아키텍처에서의 Feature는 클린 아키텍처와 유사한 방식을 취한다.
각 Feature 패키지는 최소한의 의존성을 가지며, 외부로 노출되는 인터페이스를 통해 다른 패키지와 소통한다.
패키지 간의 인터페이스를 정의함으로써 각 패키지는 독립적으로 개발, 배포, 유지보수할 수 있으며, 전체 시스템의 복잡도를 낮출 수 있다.
Feature가 다른 Feature와 강하게 결합되는 것을 피한다. 이를 통해 각 패키지는 서로 다른 비지니스 기능을 구현하면서 모듈성과 확장성을 유지할 수 있다.
그렇기 때문에 일반적으로 데이터와 도메인 로직이 한 패키지 안에 있는 것이 더 좋다. 각 Feature가 자체 데이터를 갖고 필요한 모든 비지니스 로직을 캡슐화 할 수 있으며, 독립적으로 변경 및 확장될 수 있기 때문이다.
다른 패키지에서 사용될 수 있는 공통 모델이 있는 패키지다.
각 패키지가 필요한 모델을 선택적으로 가져와 사용할 수 있으며, 패키지 간의 종속성을 최소화할 수 있다.
앱에서 전체적으로 사용되지만, 핵심이되는 부분들이다. 다른 모듈이나 라이브러리가 이를 참조하여 사용할 수 있도록 인터페이스를 제공한다. 보통 보안, 인증, 인프라 관리 등과 같이 프로젝트의 중요한 부분을 담당한다.
인증 서비스와 같은 서비스, exception이나 error 처리를 위한 규칙 및 로직, Http 통신을 위한 Http Client와 같은 것들이 들어간다.
core와 common 패키지는 프로젝트에서 공통으로 사용된다는 점에서 비슷해 보이지만 다르다. common과 core의 구분이 어렵다면, 기능을 제공하느냐, 사용되느냐를 개념적으로 구분하면 편하다. 왜냐하면 주체가 되어서 기능을 제공한다는 것이 핵심 기능이라는 근거가 될수 있기 때문이다.
결국 이 모든 것은 협업, 재사용성, 테스트를 위한 것이라 생각한다. 완벽한 정답은 없으며 각자의 상황과 성향에 따라 최선의 방법을 찾아가야 한다.