Flutter Mobile Development · บทเรียน

โครงสร้างโฟลเดอร์แบบเริ่มจากฟีเจอร์และโมโนรีโพของ Melos

จัดระเบียบฟีเจอร์เป็นแพ็กเกจอิสระและจัดการด้วยโมโนรีโพของ Melos

บทเรียน 3 จาก 413 ขั้นตอน

โครงสร้างโฟลเดอร์แบบเริ่มจากฟีเจอร์และโมโนรีโพของ Melos เป็นบทเรียน Flutter Mobile Development ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Flutter Mobile Development และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

Why Feature-First?

As a Flutter app grows, the classic layer-first structure (top-level screens/, widgets/, models/, services/) starts to hurt. To touch one feature you jump across five folders, and unrelated teams collide in the same files.

Feature-first flips this: the top level is organized by business capability (auth, cart, profile), and each feature owns its own layers internally.

  • High cohesion — everything for a feature lives together.
  • Low coupling — features depend on contracts, not each other's internals.
  • Scalability — a feature can later be extracted into its own package with almost no churn.

Anatomy of a Feature Folder

Inside lib/features/<feature>/ we mirror Clean Architecture's three layers. The domain layer is pure Dart with no Flutter or plugin imports, which is exactly what makes a feature easy to extract later.

A typical auth feature:

  • data/ — DTOs, data sources, repository implementations.
  • domain/ — entities, repository interfaces, use cases.
  • presentation/ — widgets, pages, state (BLoC/Riverpod).
lib/
├── core/                  // shared cross-cutting code
│   ├── error/
│   └── network/
└── features/
    └── auth/
        ├── data/
        │   ├── models/
        │   ├── datasources/
        │   └── repositories/
        ├── domain/
        │   ├── entities/
        │   ├── repositories/
        │   └── usecases/
        └── presentation/
            ├── bloc/
            ├── pages/
            └── widgets/

The Domain Layer Is Pure Dart

The key discipline that makes a feature portable: the domain layer imports nothing from Flutter or plugins. Entities are plain immutable Dart classes, and repository contracts are abstract interfaces.

Because this code has no framework dependencies, it is trivially unit-testable and can run on any Dart VM — including an online judge.

// domain/entities/user.dart
class User {
  final String id;
  final String email;
  const User({required this.id, required this.email});
}

// domain/repositories/auth_repository.dart
abstract class AuthRepository {
  Future<User> signIn(String email, String password);
}

void main() {
  const user = User(id: '1', email: 'ada@example.com');
  print('Loaded ${user.email}');
}

Use Cases Encode Business Rules

A use case is a single-responsibility object that orchestrates one application action. It depends only on the repository interface, never on its implementation. This keeps the presentation layer dumb and the rules testable.

The common Flutter convention is to give each use case a call method so it can be invoked like a function.

// domain/usecases/sign_in.dart
class User {
  final String email;
  const User(this.email);
}

abstract class AuthRepository {
  Future<User> signIn(String email, String password);
}

class SignIn {
  final AuthRepository repository;
  const SignIn(this.repository);

  Future<User> call(String email, String password) {
    if (!email.contains('@')) {
      throw ArgumentError('Invalid email');
    }
    return repository.signIn(email, password);
  }
}

class FakeRepo implements AuthRepository {
  @override
  Future<User> signIn(String email, String password) async => User(email);
}

void main() async {
  final signIn = SignIn(FakeRepo());
  final user = await signIn('ada@example.com', 'pw');
  print('Signed in: ${user.email}');
}

From Folders to Packages

Feature-first folders are a great first step, but folders don't enforce boundaries — any file can still import any other. The next level is to make each feature a real Dart package with its own pubspec.yaml.

Now the compiler enforces isolation: a package can only use what it explicitly declares as a dependency. This is where a monorepo comes in — many packages, one repository.

  • packages/feature_auth/
  • packages/feature_cart/
  • packages/core/
  • apps/mobile/ (the runnable Flutter app that wires features together)

What Is Melos?

Melos is a CLI tool for managing Dart/Flutter monorepos with multiple packages. It solves the pain of running pub get, tests, and version bumps across dozens of packages.

  • Bootstrap — links local packages together and resolves all dependencies in one command.
  • Scripting — define named scripts that run across every package.
  • Versioning — conventional-commit based version bumps and changelogs.

You install it once globally and configure it per repository with a melos.yaml.

dart pub global activate melos

# from the monorepo root
melos bootstrap   # or: melos bs

Configuring melos.yaml

At the repo root, melos.yaml declares the workspace name and where packages live (glob patterns). Modern Melos (3+) also supports declaring the workspace inside the root pubspec.yaml, but a dedicated file remains common.

The packages globs tell Melos which folders are managed members of the monorepo.

# melos.yaml
name: shop_workspace

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

command:
  bootstrap:
    usePubspecOverrides: true

Wiring Local Path Dependencies

Inside the app, you depend on local feature packages by name. With usePubspecOverrides: true, Melos injects the local path: overrides automatically during bootstrap, so your committed pubspec.yaml can reference versions while local development still uses source.

The result: editing a file in feature_auth is instantly visible to the app — no publishing, no copy-paste.

# apps/mobile/pubspec.yaml
name: mobile
dependencies:
  flutter:
    sdk: flutter
  feature_auth: ^1.0.0
  feature_cart: ^1.0.0
  core: ^1.0.0

# Melos generates pubspec_overrides.yaml on bootstrap:
# dependency_overrides:
#   feature_auth:
#     path: ../../packages/feature_auth

Defining Workspace Scripts

The biggest day-to-day win is scripts. You define a command once and run it across every package that matches a filter. This replaces brittle shell loops.

Common filters include --dir-exists=test (only packages that have tests) and --depends-on=core (only packages depending on a given package).

# melos.yaml
scripts:
  analyze:
    run: melos exec -- dart analyze .
    description: Analyze every package.

  test:
    run: melos exec --dir-exists=test -- flutter test
    description: Run tests only where a test/ dir exists.

# usage:
#   melos run analyze
#   melos run test

Keeping Features Decoupled

Packages enforce boundaries, but you still must design the contracts. The rule of thumb:

  • Features never import each other directly.
  • Shared contracts and primitives live in a low-level core package that everything may depend on.
  • Cross-feature navigation goes through an abstraction (a router or an event bus) injected at the app level.

This dependency direction — features → core, app → features — keeps the graph acyclic. A cycle between two feature packages will fail to resolve, which is a useful early warning.

// packages/core/lib/src/app_route.dart  (shared contract)
abstract class AppRouter {
  void goToCart();
  void goToProfile();
}

// feature_auth depends on `core`, never on feature_cart.
class LoginController {
  final AppRouter router;
  const LoginController(this.router);

  void onLoginSuccess() => router.goToCart();
}

void main() => print('Auth depends on core, not on cart.');

A Typical Developer Workflow

Putting it together, the daily loop in a Melos monorepo is short and uniform regardless of how many packages you have:

  • melos bootstrap after pulling new changes to relink packages.
  • melos run analyze and melos run test in CI and locally.
  • melos version to bump changed packages from conventional commits.

Because the app is the only Flutter-runnable target and features are plain packages, your CI can test domain logic on the pure Dart VM (fast) and only spin up Flutter for presentation tests.

# clone + first run
melos bootstrap
melos run analyze
melos run test

# ship a release
melos version          # bumps + changelogs from commits
cd apps/mobile && flutter build apk

Quick Check

You have a Melos monorepo with feature_auth and feature_cart packages. After login, auth needs to navigate the user to the cart screen. What is the cleanest way to keep the dependency graph acyclic?

Recap

You learned how to scale a Flutter codebase from folders to packages:

  • Feature-first structure groups code by business capability, each feature mirroring data/domain/presentation layers.
  • The domain layer stays pure Dart, making it portable and easy to extract.
  • Promoting features to Dart packages lets the compiler enforce boundaries that folders cannot.
  • Melos manages the resulting monorepo: bootstrap links packages, scripts run commands across all of them, and version automates releases.
  • Keep the graph acyclic: features depend on core, the app depends on features, and cross-feature concerns go through shared contracts.
เริ่มต้นได้ฟรี

เรียนรู้ Dart ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
22
บทเรียน
88

คำถามที่พบบ่อย

บทเรียน “โครงสร้างโฟลเดอร์แบบเริ่มจากฟีเจอร์และโมโนรีโพของ Melos” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “โครงสร้างโฟลเดอร์แบบเริ่มจากฟีเจอร์และโมโนรีโพของ Melos” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Flutter Mobile Development ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “โครงสร้างโฟลเดอร์แบบเริ่มจากฟีเจอร์และโมโนรีโพของ Melos”

จัดระเบียบฟีเจอร์เป็นแพ็กเกจอิสระและจัดการด้วยโมโนรีโพของ Melos คุณปฏิบัติ Flutter Mobile Development ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Flutter Mobile Development หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Flutter Mobile Development บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “โครงสร้างโฟลเดอร์แบบเริ่มจากฟีเจอร์และโมโนรีโพของ Melos” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Flutter Mobile Development นี้ได้ไหม

ได้ บทเรียน Flutter Mobile Development ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ขอบเขตระหว่างเลเยอร์โดเมน ข้อมูล และการนำเสนอ
  2. การฉีดการพึ่งพาด้วย get_it และ injectable
  3. โครงสร้างโฟลเดอร์แบบเริ่มจากฟีเจอร์และโมโนรีโพของ Melos
  4. Either ประเภทความล้มเหลว และการจัดการข้อผิดพลาดเชิงฟังก์ชัน
← กลับไปที่ Flutter Mobile Development