Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot
Järjestä ominaisuudet itsenäisiksi paketeiksi ja hallitse niitä Melos-monorepossa.
Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot on ilmainen Mobiilikehitys Flutterilla-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu Mobiilikehitys Flutterilla-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Mobiilikehitys Flutterilla-kurssilla on yhteensä 4 oppituntia.
Miksi ominaisuuslähtöinen rakenne?
Flutter-sovelluksen kasvaessa perinteinen kerroslähtöinen rakenne (ylätason screens/-, widgets/-, models/- ja services/-hakemistot) alkaa haitata kehitystä. Yhden ominaisuuden muuttaminen edellyttää siirtymistä viiden hakemiston välillä, ja toisistaan riippumattomat tiimit joutuvat muokkaamaan samoja tiedostoja.
Ominaisuuslähtöinen rakenne kääntää tämän asetelman: ylätaso järjestetään liiketoimintakyvykkyyksien (auth, cart, profile) mukaan, ja jokainen ominaisuus sisältää omat kerroksensa.
- Tiivis koheesio — kaikki ominaisuuteen liittyvä on yhdessä paikassa.
- Löyhä kytkentä — ominaisuudet riippuvat sopimuksista eivätkä toistensa sisäisistä toteutuksista.
- Skaalautuvuus — ominaisuus voidaan myöhemmin irrottaa omaksi paketiksi lähes ilman muutoksia.
Ominaisuushakemiston rakenne
Hakemistossa lib/features/<feature>/ Clean Architecture -arkkitehtuurin kolme kerrosta ovat peilattuina. domain-kerros on puhdasta Dartia ilman Flutter- tai plugin-tuonteja, minkä ansiosta ominaisuus on helppo irrottaa myöhemmin.
Tyypillinen auth-ominaisuus:
data/— DTO:t, tietolähteet ja repositorioiden toteutukset.domain/— entiteetit, repositorioiden rajapinnat ja käyttötapaukset.presentation/— widgetit, sivut ja tila (BLoC/Riverpod).
lib/
├── core/ // shared cross-cutting code
│ ├── error/
│ └── network/
└── features/
└── auth/
├── data/
│ ├── models/
│ ├── datasources/
│ └── repositories/
├── domain/
│ ├── entities/
│ ├── repositories/
│ └── usecases/
└── presentation/
├── bloc/
├── pages/
└── widgets/Domain-kerros on puhdasta Dartia
Ominaisuuden siirrettävyyden kannalta keskeinen periaate on, että domain-kerros ei tuo riippuvuuksia mistään Flutterista tai plugineista. Entiteetit ovat tavallisia muuttumattomia Dart-luokkia, ja repositorioiden sopimukset ovat abstrakteja rajapintoja.
Koska tämä koodi ei riipu frameworkista, sitä on erittäin helppo testata yksikkötesteillä, ja se toimii missä tahansa Dart-virtuaalikoneessa — myös verkossa toimivassa arviointijärjestelmässä.
// 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}');
}Käyttötapaukset koodaavat liiketoimintasäännöt
Käyttötapaus on yhden vastuualueen olio, joka ohjaa yhtä sovelluksen toimintoa. Se riippuu vain repositorion rajapinnasta eikä koskaan sen toteutuksesta. Tämä pitää presentation-kerroksen yksinkertaisena ja säännöt testattavina.
Flutterissa yleinen käytäntö on antaa jokaiselle käyttötapaukselle call-metodi, jotta sitä voidaan kutsua funktion tavoin.
// 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}');
}Hakemistoista paketeiksi
Ominaisuuksien mukaan järjestellyt kansiot ovat hyvä ensimmäinen askel, mutta kansiot eivät pakota rajoja — mikä tahansa tiedosto voi edelleen importida minkä tahansa muun tiedoston. Seuraava taso on tehdä jokaisesta ominaisuudesta oikea Dart-paketti, jolla on oma pubspec.yaml-tiedostonsa.
Nyt kääntäjä pakottaa eristyksen: paketti voi käyttää vain sitä, minkä se ilmoittaa nimenomaisesti riippuvuudekseen. Tässä monorepo tulee mukaan — useita paketteja yhdessä repositoriossa.
packages/feature_auth/packages/feature_cart/packages/core/apps/mobile/(ajettava Flutter-sovellus, joka yhdistää ominaisuudet)
Mikä Melos on?
Melos on CLI-työkalu useita paketteja sisältävien Dart-/Flutter-monorepojen hallintaan. Se helpottaa pub get-komennon, testien ja versionumeroiden päivitysten suorittamista kymmenissä paketeissa.
- Bootstrap — linkittää paikalliset paketit toisiinsa ja ratkaisee kaikki riippuvuudet yhdellä komennolla.
- Skriptaus — määrittää nimettyjä skriptejä, jotka suoritetaan jokaisessa paketissa.
- Versiointi — conventional commit -käytäntöön perustuvat versionumeropäivitykset ja muutoslokit.
Asennat sen kerran globaalisti ja määrität sen repositoriokohtaisesti melos.yaml-tiedostolla.
dart pub global activate melos
# from the monorepo root
melos bootstrap # or: melos bsmelos.yaml-tiedoston määrittäminen
Repositorion juuressa oleva melos.yaml ilmoittaa työtilan nimen ja pakettien sijainnin (glob-kuviot). Nykyaikainen Melos (3+) tukee myös työtilan ilmoittamista juuren pubspec.yaml-tiedostossa, mutta erillinen tiedosto on edelleen yleinen.
packages-glob-kuviot kertovat Melosille, mitkä kansiot ovat monorepon hallinnoituja jäseniä.
# melos.yaml
name: shop_workspace
packages:
- apps/**
- packages/**
command:
bootstrap:
usePubspecOverrides: truePaikallisten polkuriippuvuuksien yhdistäminen
Sovelluksessa viittaat paikallisiin ominaisuuspaketteihin niiden nimellä. Kun usePubspecOverrides: true on käytössä, Melos lisää paikalliset path:-ohitukset automaattisesti bootstrap-komennon aikana. Näin versionumeroihin viittaava, repositorioon tallennettu pubspec.yaml voi säilyä ennallaan, vaikka paikallisessa kehityksessä käytetään lähdekoodia.
Tuloksena on, että feature_auth-paketin tiedoston muokkaus näkyy sovelluksessa heti — julkaisemista tai kopiointia ja liittämistä ei tarvita.
# 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_authTyötilan skriptien määrittäminen
Suurin päivittäinen hyöty ovat skriptit. Määrität komennon kerran ja suoritat sen jokaisessa suodattimen täyttävässä paketissa. Tämä korvaa epäluotettavat shell-silmukat.
Yleisiä suodattimia ovat --dir-exists=test (vain paketit, joissa on testejä) ja --depends-on=core (vain paketit, jotka riippuvat tietystä paketista).
# 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 testOminaisuuksien pitäminen erillään
Paketit pakottavat rajat, mutta sinun on silti suunniteltava sopimukset. Nyrkkisääntö:
- Ominaisuudet eivät koskaan tuo toisiaan suoraan
import-lauseella. - Jaetut sopimukset ja perusrakenteet sijaitsevat matalan tason
core-paketissa, josta kaikki voivat riippua. - Ominaisuuksien välinen navigointi kulkee abstraktion (reitittimen tai tapahtumaväylän) kautta, ja tämä abstraktio injektoidaan
app-tasolla.
Tämä riippuvuussuunta — ominaisuudet → core, app → ominaisuudet — pitää graafin syklittömänä. Kahden ominaisuuspaketin välinen sykli ei ratkea, mikä on hyödyllinen varhainen varoitus.
// 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.');Tyypillinen kehittäjän työnkulku
Kun kaikki yhdistetään, Melos-monorepon päivittäinen työnkulku on lyhyt ja yhdenmukainen riippumatta pakettien määrästä:
melos bootstrapuusien muutosten hakemisen jälkeen pakettien linkittämiseksi uudelleen.melos run analyzejamelos run testCI:ssä ja paikallisesti.melos versionmuuttuneiden pakettien versionumeroiden kasvattamiseksi conventional commit -viesteistä.
Koska sovellus on ainoa Flutterilla ajettava kohde ja ominaisuudet ovat tavallisia paketteja, CI voi testata toimialuelogiikkaa puhtaassa Dart-virtuaalikoneessa (nopeasti) ja käynnistää Flutterin vain esityskerroksen testejä varten.
# 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 apkPikatarkistus
Sinulla on Melos-monorepo, jossa ovat paketit feature_auth ja feature_cart. Kirjautumisen jälkeen auth-ominaisuuden on navigoitava käyttäjä ostoskorinäkymään. Mikä on siistein tapa pitää riippuvuusgraafi syklittömänä?
Kertaus
Opit skaalaamaan Flutter-koodikantaa kansioista paketeiksi:
- Ominaisuuksien mukaan järjestelty rakenne ryhmittelee koodin liiketoimintaominaisuuden perusteella, ja jokainen ominaisuus jäljittelee data-, toimialue- ja esityskerroksia.
- Toimialuekerros pysyy puhtaana Dartina, joten se on siirrettävä ja helppo irrottaa.
- Ominaisuuksien muuttaminen Dart-paketeiksi antaa kääntäjän pakottaa rajat, joita kansiot eivät pysty pakottamaan.
- Melos hallitsee syntyvää monorepoa:
bootstraplinkittää paketit,scriptssuorittavat komentoja kaikissa paketeissa javersionautomatisoi julkaisut. - Pidä graafi syklittömänä: ominaisuudet riippuvat
core-paketista, sovellus ominaisuuksista ja ominaisuuksien väliset asiat kulkevat jaettujen sopimusten kautta.
Opi Dart tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 22
- Oppitunnit
- 88
Usein kysytyt kysymykset
Onko oppitunti ”Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot” ilmainen?
Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Mobiilikehitys Flutterilla-oppimispolun 3 oppituntia, myös oppitunnin “Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Mobiilikehitys Flutterilla-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot”?
Järjestä ominaisuudet itsenäisiksi paketeiksi ja hallitse niitä Melos-monorepossa. Harjoittelet Mobiilikehitys Flutterilla-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Mobiilikehitys Flutterilla-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Mobiilikehitys Flutterilla-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.
Kuinka kauan ”Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Mobiilikehitys Flutterilla-oppitunnilla?
Kyllä. Jokainen Mobiilikehitys Flutterilla-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Toimialue-, data- ja esityskerrosten rajat
- Riippuvuuksien injektointi get_itillä ja injectablella
- Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot
- Either, virhetyypit ja funktionaalinen virheenkäsittely