iBetter Books
수정

프로젝트 폴더 구조 설계

코드를 어디에 두느냐가 곧 유지보수성을 결정합니다. 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 runbin/ 폴더의 파일을 직접 실행합니다.

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 라이브러리 시스템을 세밀하게 제어하는 방법을 배웁니다.