Feature-først-mappestruktur og Melos-monorepos
Organisér funktioner som uafhængige pakker, og administrér dem med et Melos-monorepo.
Feature-først-mappestruktur og Melos-monorepos er en gratis Mobiludvikling med Flutter-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Mobiludvikling med Flutter, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.
Hvorfor feature-først?
Efterhånden som en Flutter-app vokser, begynder den klassiske lag-først-struktur (mapper på øverste niveau som screens/, widgets/, models/, services/) at give problemer. For at ændre én feature skal du hoppe mellem fem mapper, og forskellige teams kommer i vejen for hinanden i de samme filer.
Feature-først vender dette om: Det øverste niveau organiseres efter forretningsområde (auth, kurv, profil), og hver feature ejer sine egne lag internt.
- Høj kohæsion — alt til en feature ligger samlet.
- Lav kobling — features afhænger af kontrakter og ikke af hinandens interne detaljer.
- Skalerbarhed — en feature kan senere udtrækkes til sin egen pakke med næsten ingen omstrukturering.
En feature-mappes opbygning
Inde i lib/features/<feature>/ afspejler vi Clean Architectures tre lag. Domænelaget er ren Dart uden Flutter- eller plugin-imports, hvilket netop gør en feature nem at udtrække senere.
En typisk auth-feature:
data/— DTO'er, datakilder og repository-implementeringer.domain/— entiteter, repository-interfaces og use cases.presentation/— widgets, sider og tilstand (BLoC/Riverpod).
lib/
├── core/ // shared cross-cutting code
│ ├── error/
│ └── network/
└── features/
└── auth/
├── data/
│ ├── models/
│ ├── datasources/
│ └── repositories/
├── domain/
│ ├── entities/
│ ├── repositories/
│ └── usecases/
└── presentation/
├── bloc/
├── pages/
└── widgets/Domænelaget er ren Dart
Den afgørende disciplin, der gør en feature portabel, er, at domænelaget ikke importerer noget fra Flutter eller plugins. Entiteter er almindelige uforanderlige Dart-klasser, og repository-kontrakter er abstrakte interfaces.
Fordi denne kode ikke har afhængigheder til frameworks, kan den uden videre enhedstestes og køre på enhver Dart-VM — også i en onlinebedømmer.
// 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 indkoder forretningsregler
Et use case er et objekt med ét ansvarsområde, der orkestrerer én handling i applikationen. Det afhænger kun af repository-interfacet og aldrig af implementeringen. Det holder præsentationslaget enkelt og reglerne testbare.
Den almindelige Flutter-konvention er at give hvert use case en call-metode, så det kan kaldes som en funktion.
// 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}');
}Fra mapper til pakker
Feature-first-mapper er et godt første skridt, men mapper håndhæver ikke grænser – alle filer kan stadig importere alle andre. Det næste niveau er at gøre hver feature til en rigtig Dart-pakke med sin egen pubspec.yaml.
Nu håndhæver compileren isolation: En pakke kan kun bruge det, den udtrykkeligt erklærer som en afhængighed. Det er her, et monorepo kommer ind i billedet – mange pakker, ét repository.
packages/feature_auth/packages/feature_cart/packages/core/apps/mobile/(den kørbare Flutter-app, der forbinder features)
Hvad er Melos?
Melos er et CLI-værktøj til at administrere Dart-/Flutter-monorepoer med flere pakker. Det løser besværet med at køre pub get, test og versionsopdateringer på tværs af dusinvis af pakker.
- Bootstrap – sammenkæder lokale pakker og løser alle afhængigheder med én kommando.
- Scripting – definér navngivne scripts, der køres på tværs af alle pakker.
- Versioning – versionsopdateringer og ændringslogge baseret på conventional commits.
Du installerer det én gang globalt og konfigurerer det pr. repository med en melos.yaml.
dart pub global activate melos
# from the monorepo root
melos bootstrap # or: melos bsKonfiguration af melos.yaml
I roden af dit repository erklærer melos.yaml arbejdsområdets navn og hvor pakkerne findes (glob-mønstre). Moderne Melos (3+) understøtter også, at arbejdsområdet erklæres i rodens pubspec.yaml, men en dedikeret fil er stadig almindelig.
packages-globberne fortæller Melos, hvilke mapper der er administrerede medlemmer af monorepoet.
# melos.yaml
name: shop_workspace
packages:
- apps/**
- packages/**
command:
bootstrap:
usePubspecOverrides: trueSammenkædning af lokale sti-afhængigheder
I appen afhænger du af lokale feature-pakker via deres navn. Med usePubspecOverrides: true indsætter Melos automatisk de lokale path:-tilsidesættelser under bootstrap, så din committede pubspec.yaml kan referere til versioner, mens lokal udvikling stadig bruger kildekoden.
Resultatet er, at ændringer i en fil i feature_auth straks kan ses i appen – ingen publicering og ingen kopiering og indsættelse.
# 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_authDefinition af scripts til arbejdsområdet
Den største fordel i hverdagen er scripts. Du definerer en kommando én gang og kører den på tværs af alle pakker, der matcher et filter. Det erstatter skrøbelige shell-løkker.
Almindelige filtre omfatter --dir-exists=test (kun pakker, der har test) og --depends-on=core (kun pakker, der afhænger af en bestemt pakke).
# 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 testSådan holdes features løst koblede
Pakker håndhæver grænser, men du skal stadig designe kontrakterne. En god tommelfingerregel er:
- Features importerer aldrig hinanden direkte.
- Delte kontrakter og primitive typer ligger i en lavniveau-
core-pakke, som alt kan afhænge af. - Navigation på tværs af features går gennem en abstraktion (en router eller en eventbus), der indsættes på
app-niveau.
Denne afhængighedsretning – features → core, app → features – holder grafen acyklisk. En cyklus mellem to feature-pakker kan ikke løses, hvilket er en nyttig tidlig advarsel.
// 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.');En typisk udviklerarbejdsgang
Samlet set er den daglige løkke i et Melos-monorepo kort og ensartet, uanset hvor mange pakker du har:
melos bootstrapefter du har hentet nye ændringer, så pakkerne sammenkædes igen.melos run analyzeogmelos run testi CI og lokalt.melos versionfor at opdatere versionen på ændrede pakker ud fra conventional commits.
Fordi appen er det eneste mål, der kan køres med Flutter, og features er almindelige pakker, kan din CI teste domænelogik på den rene Dart-VM (hurtigt) og kun starte Flutter til præsentationstest.
# 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 apkHurtigt tjek
Du har et Melos-monorepo med pakkerne feature_auth og feature_cart. Efter login skal auth navigere brugeren til cart-skærmen. Hvad er den reneste måde at holde afhængighedsgrafen acyklisk på?
Opsummering
Du har lært, hvordan du skalerer en Flutter-kodebase fra mapper til pakker:
- En Feature-first-struktur grupperer kode efter forretningsfunktionalitet, hvor hver feature afspejler lagene data/domæne/præsentation.
- Domænelaget forbliver ren Dart, hvilket gør det portabelt og nemt at udtrække.
- Ved at gøre features til Dart-pakker kan compileren håndhæve grænser, som mapper ikke kan.
- Melos administrerer det resulterende monorepo:
bootstrapsammenkæder pakker,scriptskører kommandoer på tværs af dem alle, ogversionautomatiserer udgivelser. - Hold grafen acyklisk: Features afhænger af
core, appen afhænger af features, og forhold på tværs af features går gennem delte kontrakter.
Lær Dart med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 22
- Lektioner
- 88
Ofte stillede spørgsmål
Er lektionen “Feature-først-mappestruktur og Melos-monorepos” gratis?
Ja — alle 3 lektioner i læringssporet Mobiludvikling med Flutter, inklusive “Feature-først-mappestruktur og Melos-monorepos”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Feature-først-mappestruktur og Melos-monorepos”?
Organisér funktioner som uafhængige pakker, og administrér dem med et Melos-monorepo. Du øver dig i Mobiludvikling med Flutter med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Mobiludvikling med Flutter?
Der kræves ingen tidligere erfaring. Mobiludvikling med Flutter på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.
Hvor lang tid tager lektionen “Feature-først-mappestruktur og Melos-monorepos”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Mobiludvikling med Flutter-lektion?
Ja. Alle Mobiludvikling med Flutter-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Grænser mellem domæne-, data- og præsentationslag
- Dependency injection med get_it og injectable
- Feature-først-mappestruktur og Melos-monorepos
- Either, fejltyper og funktionel fejlhåndtering