Mobiilikehitys Flutterilla · Oppitunti

Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot

Järjestä ominaisuudet itsenäisiksi paketeiksi ja hallitse niitä Melos-monorepossa.

Oppitunti 3/413 vaihetta

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 bs

melos.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: true

Paikallisten 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_auth

Työ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 test

Ominaisuuksien 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 bootstrap uusien muutosten hakemisen jälkeen pakettien linkittämiseksi uudelleen.
  • melos run analyze ja melos run test CI:ssä ja paikallisesti.
  • melos version muuttuneiden 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 apk

Pikatarkistus

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: bootstrap linkittää paketit, scripts suorittavat komentoja kaikissa paketeissa ja version automatisoi julkaisut.
  • Pidä graafi syklittömänä: ominaisuudet riippuvat core-paketista, sovellus ominaisuuksista ja ominaisuuksien väliset asiat kulkevat jaettujen sopimusten kautta.
Aloita maksutta

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

  1. Toimialue-, data- ja esityskerrosten rajat
  2. Riippuvuuksien injektointi get_itillä ja injectablella
  3. Ominaisuuslähtöinen kansiorakenne ja Melos-monorepot
  4. Either, virhetyypit ja funktionaalinen virheenkäsittely
← Takaisin: Mobiilikehitys Flutterilla