프로젝트 폴더 구조 설계
코드를 어디에 두느냐가 곧 유지보수성을 결정합니다. Dart는 폴더 구조에 대한 명확한 관례를 가지고 있습니다. lib/, bin/, test/, example/의 역할을 정확히 알고, src/ 패턴과 배럴 파일(barrel file)을 활용하면 규모가 커져도 정리된 코드베이스를 유지할 수 있습니다.
표준 폴더 구조
my_project/
├── bin/ ← 실행 가능한 스크립트 (외부 공개)
│ └── server.dart
├── lib/ ← 라이브러리 코드 (외부 공개)
│ ├── my_project.dart ← 패키지 진입점 (barrel file)
│ └── src/ ← 내부 구현 (외부 비공개)
│ ├── models/
│ ├── services/
│ └── utils/
├── test/ ← 테스트 코드
│ ├── models/
│ └── services/
├── example/ ← 사용 예제 (패키지 개발 시)
│ └── main.dart
├── tool/ ← 빌드/자동화 스크립트
│ └── generate.dart
├── doc/ ← API 문서 (생성된 파일)
├── pubspec.yaml
├── pubspec.lock
├── analysis_options.yaml
├── CHANGELOG.md
├── README.md
└── LICENSE
bin/ 폴더
외부에서 실행할 수 있는 Dart 스크립트를 놓는 곳입니다.
bin/
├── server.dart ← 메인 서버 진입점
├── migrate.dart ← DB 마이그레이션 도구
└── seed.dart ← 데이터 시드 스크립트
dart run은 bin/ 폴더의 파일을 직접 실행합니다.
dart run bin/server.dartdart run server # bin/server.dart와 동일 (패키지명이 'server'가 아닌 경우 주의)
bin/ 파일은 외부에서 바로 접근하는 파일이므로, 비즈니스 로직은 넣지 않고 lib/의 코드를 가져와 조합하는 역할만 합니다.
// bin/server.dart — 얇은 진입점
import 'package:my_api/server.dart';
void main() async {
final server = MyServer();
await server.start(port: 8080);
}
lib/ 폴더
패키지의 공개 API가 들어갑니다. import 'package:my_project/...'으로 접근 가능한 파일들입니다.
lib/
├── my_project.dart ← 패키지 진입점 (barrel file)
└── src/ ← 내부 구현
├── models/
│ ├── user.dart
│ └── post.dart
├── services/
│ ├── user_service.dart
│ └── post_service.dart
├── repositories/
│ └── user_repository.dart
└── utils/
├── string_utils.dart
└── date_utils.dart
src/ 패턴
lib/src/는 구현 세부사항을 숨기는 관례입니다.
lib/my_project.dart→ 공개 API (export로 선택적 노출)lib/src/...→ 내부 구현 (직접 import 가능하지만 관례적으로 외부 노출 안 함)
// lib/src/models/user.dart — 내부 구현
class User {
final int id;
final String name;
final String email;
const User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
};
}
배럴 파일 (Barrel File)
여러 파일을 하나의 진입점으로 묶어 export하는 파일입니다.
// lib/my_project.dart — 배럴 파일
library my_project;
export 'src/models/user.dart';
export 'src/models/post.dart';
export 'src/services/user_service.dart';
export 'src/services/post_service.dart';
// src/utils/는 내부용이므로 export 안 함
배럴 파일 덕분에 사용자는 하나의 import만으로 전체 API를 사용할 수 있습니다.
// 배럴 파일 없을 때
import 'package:my_project/src/models/user.dart';
import 'package:my_project/src/models/post.dart';
import 'package:my_project/src/services/user_service.dart';
// 배럴 파일 있을 때
import 'package:my_project/my_project.dart';
폴더별 배럴 파일을 만들고 상위에서 재export하는 방식도 자주 씁니다.
// lib/src/models/models.dart — 모델 배럴
export 'user.dart';
export 'post.dart';
export 'comment.dart';
// lib/my_project.dart — 최상위 배럴
export 'src/models/models.dart';
export 'src/services/services.dart';
test/ 폴더
lib/의 구조를 그대로 미러링합니다.
test/
├── models/
│ ├── user_test.dart ← lib/src/models/user.dart 테스트
│ └── post_test.dart
├── services/
│ └── user_service_test.dart
└── helpers/
└── test_helpers.dart ← 공통 테스트 유틸리티
# 전체 테스트 실행dart test# 특정 파일 실행dart test test/models/user_test.dart# 특정 테스트 이름 실행dart test --name 'User fromJson'
example/ 폴더
공개 패키지를 만들 때 사용 예제를 보여주는 폴더입니다. pub.dev에서 점수에 반영됩니다.
example/
├── main.dart ← 기본 사용 예제
└── advanced.dart ← 고급 사용 예제
// example/main.dart
import 'package:my_project/my_project.dart';
void main() async {
final service = UserService();
final user = await service.findById(1);
print(user.name);
}
대규모 프로젝트 구조
기능 단위로 폴더를 나누는 Feature-first 구조입니다.
lib/
├── my_app.dart ← 최상위 배럴
├── src/
│ ├── core/ ← 공통 기반 코드
│ │ ├── exceptions/
│ │ ├── extensions/
│ │ └── utils/
│ ├── features/ ← 기능별 모듈
│ │ ├── auth/
│ │ │ ├── models/
│ │ │ ├── services/
│ │ │ └── repositories/
│ │ ├── posts/
│ │ │ ├── models/
│ │ │ ├── services/
│ │ │ └── repositories/
│ │ └── users/
│ └── infrastructure/ ← 외부 시스템 연동
│ ├── database/
│ ├── cache/
│ └── http/
.dart_tool/ 폴더
dart pub get 실행 시 자동으로 생성되는 도구 메타데이터 폴더입니다. .gitignore에 추가하지 않아도 되지만, package_config.json은 git에 커밋하는 것이 좋습니다.
# .gitignore
.dart_tool/
build/
doc/api/
*.js
*.js.map
정리
이번 챕터에서 다룬 내용입니다.
bin/은 실행 진입점,lib/은 공개 API,lib/src/는 내부 구현,test/는 테스트 코드입니다.lib/src/패턴으로 내부 구현을 캡슐화합니다.- 배럴 파일(
lib/패키지명.dart)로 공개 API를 하나의 진입점으로 묶습니다. test/는lib/의 구조를 미러링합니다.- 규모가 커지면 Feature-first 구조로 기능별로 폴더를 나눕니다.
다음 챕터에서는 library, part/part of, export로 Dart 라이브러리 시스템을 세밀하게 제어하는 방법을 배웁니다.