FlutterRazvoj mobilnih aplikacijaArhitekturaClean ArchitectureTestiranjeDart

Flutter arhitektura aplikacije koja se skalira: Clean Architecture vs Feature-First (s realnim strukturama mapa)

Adrijan Omičević··14 min čitanja
Share

# Što ćete naučiti#

Ovaj vodič uspoređuje dva provjerena pristupa arhitekturi Flutter aplikacija koja se skalira u stvarnim proizvodima: Clean Architecture i Feature-First. Vidjet ćete strukture mapa koje možete copy-pasteati, stroge granice ovisnosti i strategiju testiranja koja refaktore čini sigurnima.

Ako još odlučujete o upravljanju stanjem (state management), uskladite arhitekturu i odabir state pristupa što ranije. Uparite ovaj vodič s našom analizom aktualnih najboljih praksi u članku Flutter state management u 2026.

# Zašto je arhitektura Flutter aplikacije važna u 2026.#

Skalabilna arhitektura Flutter aplikacije smanjuje cijenu promjena. Kako codebase raste, usko grlo postaje koordinacija i rizik regresija, a ne UI framework.

Konkretan utjecaj koji viđamo u timovima koji isporučuju:

  • Timovi bez jasnih granica obično usporavaju nakon otprilike 20.000 do 50.000 linija Darta jer “male promjene” počnu se prelijevati preko ekrana, servisa i modela.
  • Automatizirani testovi isplate se kada često isporučujete. Industrijska istraživanja dosljedno pokazuju da visoko-performantni timovi snažno ovise o automatiziranom testiranju i CI-u kako bi održali ritam, posebno kod tjednih ili dnevnih izdanja.
  • Mobilne aplikacije imaju veći QA overhead nego web. Čista granica između UI-a i poslovne logike drastično smanjuje debugging vezan uz uređaje i ubrzava cikluse pregleda (review).

Cilj nije “savršena arhitektura”. Cilj su predvidljive promjene i brz onboarding.

# Dva pristupa koja se skaliraju#

Oba pristupa rade. Pravi odabir ovisi o složenosti domene, veličini tima i učestalosti izdanja.

DimenzijaClean ArchitectureFeature-First arhitektura
Primarni princip organizacijeSlojevi po odgovornostiModuli po featureu
Najbolje zaSložena domena, dugovječne aplikacije, regulirani proizvodiBrza iteracija, više vertikalnih sliceova, produktno vođen razvoj
Tipično odgovara timu6 do 20+ inženjera2 do 10 inženjera
Sigurnost refaktoraVrlo visoka uz jake graniceVisoka ako se granice provode
Početni trošak postaveSrednji do visokNizak do srednji
Česti način propadanjaPreviše boilerplatea za jednostavne aplikacijeCoupling između featurea i duplicirana logika

🎯 Ključna poruka: Odaberite arhitekturu koja minimizira vaš budući trošak promjena, ne onu koja najljepše izgleda na dijagramu.

# Preduvjeti i pretpostavke#

Ovaj članak pretpostavlja:

ZahtjevPreporukaNapomena
FlutterStable channelU timu držite SDK konzistentnim preko FVM-a
Upravljanje stanjemBilo koji moderni pristupRiverpod i Bloc su česti; primjeri su neutralni
DIOpcionalno, ali korisnoget_it ili constructor injection
BackendBilo kojiAko koristite Firebase, pogledajte Flutter Firebase tutorial za production uzorke postave

# Pristup 1: Clean Architecture u Flutteru#

Clean Architecture tipično znači da odvajate odgovornosti u slojeve s pravilima ovisnosti. U Flutteru se dobro preslikava na tri sloja po featureu ili po modulu:

  • Presentation: UI, state, kontroleri, view modeli.
  • Domain: poslovna pravila, entiteti, use caseovi, sučelja repozitorija.
  • Data: API klijenti, perzistencija, DTO-i, implementacije repozitorija.

Pravila ovisnosti za Clean Architecture#

Osnovno pravilo: ovisnosti idu prema unutra.

  • Presentation ovisi o Domain.
  • Data ovisi o Domain.
  • Domain ne ovisi ni o čemu iz koda aplikacije, samo o Dart core i malim čistim paketima.

Time sprječavate da poslovna logika “pokupi” detalje frameworka, što olakšava testiranje i ponovnu upotrebu.

Struktura mapa za Clean Architecture (stvarni primjer)#

Ovo je praktična struktura koja radi za srednje do velike aplikacije. Organizirana je po slojevima, a zatim po featureu unutar svakog sloja.

Text
lib/
  app/
    di/
      injector.dart
    routing/
      app_router.dart
    theme/
      app_theme.dart
    bootstrap.dart
  core/
    error/
      failures.dart
      exceptions.dart
    network/
      dio_client.dart
      connectivity_service.dart
    storage/
      secure_storage.dart
      preferences.dart
    utils/
      date_time.dart
      validators.dart
  features/
    auth/
      data/
        datasources/
          auth_remote_data_source.dart
          auth_local_data_source.dart
        dto/
          login_request_dto.dart
          user_dto.dart
        repositories/
          auth_repository_impl.dart
      domain/
        entities/
          user.dart
        repositories/
          auth_repository.dart
        usecases/
          login.dart
          logout.dart
          get_current_user.dart
      presentation/
        controllers/
          auth_controller.dart
        pages/
          login_page.dart
        widgets/
          login_form.dart
    payments/
      data/
      domain/
      presentation/
  main.dart
test/
  features/
    auth/
      domain/
        login_test.dart
      data/
        auth_repository_impl_test.dart
      presentation/
        login_page_test.dart

Ova struktura se dobro skalira jer svaka datoteka ima jasan “dom”. Također podržava postupno izdvajanje u Dart pakete kasnije, bez promjene konceptualnog modela.

Granice Clean Architecture u praksi#

Čist set import pravila je ono što sprječava da se ova arhitektura raspadne.

SlojSmije importatiNe smije importati
PresentationDomain, core, Flutter UIData implementacije, DTO-e
DomainDart core, funkcionalne pomoćnikeFlutter, Dio, Firebase, shared_preferences
DataDomain, core, vanjske SDK-ovePresentation

⚠️ Upozorenje: Najčešće kršenje je korištenje UserDto u UI-u jer “ima polja koja trebate”. Time vežete UI uz API shape i promjene na backendu postaju puno skuplje.

Entiteti vs DTO-i vs UI modeli#

Odvojite ove koncepte ili ćete kasnije platiti cijenu.

Tip modelaNalazi se uSvrhaMijenja se kada
EntityDomainStabilno poslovno značenjeMijenjaju se poslovna pravila
DTODataOdgovara API ili bazi (schema)Mijenja se API ili storage
UI modelPresentationPrilagođen ekranuMijenja se UI ili UX

Ako preskočite UI modele, i dalje možete držati Entities stabilnima i mapirati ih prema UI-u po potrebi. Ključ je: ne dopustite da DTO-i “procure” izvan data sloja.

Primjer: Granica repozitorija i mapiranje#

Ovaj primjer prikazuje sučelje repozitorija u Domain i njegovu implementaciju u Data. UI vidi samo Domain tipove.

Dart
// lib/features/auth/domain/repositories/auth_repository.dart
abstract class AuthRepository {
  Future<User> login({
    required String email,
    required String password,
  });
 
  Future<void> logout();
}
Dart
// lib/features/auth/data/repositories/auth_repository_impl.dart
class AuthRepositoryImpl implements AuthRepository {
  AuthRepositoryImpl(this._remote);
 
  final AuthRemoteDataSource _remote;
 
  @override
  Future<User> login({
    required String email,
    required String password,
  }) async {
    final dto = await _remote.login(email: email, password: password);
    return User(id: dto.id, email: dto.email);
  }
 
  @override
  Future<void> logout() => _remote.logout();
}

Linija mapiranja izgleda dosadno i upravo je to poanta. To je “šav” na kojem upijate churn s backenda.

Strategija testiranja za Clean Architecture#

Clean Architecture podržava jednostavnu piramidu testiranja jer se svaki sloj može testirati izolirano.

Tip testaMetaTipični alatiŠto validirate
Unit testoviDomain use caseovi, entitetitest, fake repozitorijiPoslovna pravila, rubni slučajevi
Unit testoviData mapiranje i repo implementacijemocktail, lokalni fakesDTO konverzije, obrada grešaka
Widget testoviPresentation stanjaflutter_testUI za loading, error, success
Integracijski testoviEnd-to-end tokoviintegration_testLogin, checkout, kritični funnel-i

Praktičan omjer za produktni tim je otprilike 70 posto unit testova, 20 posto widget testova, 10 posto integracijskih testova. Točan omjer ovisi o tome koliko je UI dinamičan i koliko su funnel-i kritični.

💡 Savjet: Krenite testirati Domain use caseove prvo. Mijenjaju se rjeđe od UI-a i daju najbrži feedback po napisanom testu.

Kada je Clean Architecture pravi izbor#

Odaberite Clean Architecture kada su barem dvije od ovih tvrdnji istinite:

  • Vaša domena je složena: dozvole, pravila naplate, workflow-i, offline-first sinkronizacija ili agresivno cacheiranje.
  • Više timova radi paralelno i trebate stroge granice kako biste izbjegli merge konflikte.
  • Gradite za životni vijek 2+ godine gdje su onboarding i sigurnost refaktora važniji od današnje brzine.
  • Trebate visoku pokrivenost testovima jer su izdanja rizična ili regulirana.

Ako rano procjenjujete scope i budžet, arhitekturne odluke utječu na to koliko brzo možete isporučiti MVP i koliko ćete platiti iteracije. Koristite okvir troška kao što opisujemo u mobile app MVP cost.

# Pristup 2: Feature-First arhitektura u Flutteru#

Feature-First organizira codebase oko vertikalnih sliceova: auth, onboarding, profile, feed, itd. Svaki feature sadrži sve što mu treba, što smanjuje kretanje kroz module i čini ownership jasnijim.

Ovaj pristup se skalira kada provodite granice i imate mali shared core za cross-cutting concerns.

Struktura mapa za Feature-First (stvarni primjer)#

Ovo je provjerena struktura za timove koji često isporučuju. Drži svaki feature samodostatnim i koristi shared područje samo za uistinu globalne servise.

Text
lib/
  app/
    main.dart
    bootstrap.dart
    router.dart
  shared/
    http/
      api_client.dart
    storage/
      secure_storage.dart
    analytics/
      analytics_service.dart
    ui/
      components/
        primary_button.dart
      theme/
        app_theme.dart
    utils/
      result.dart
      debounce.dart
  features/
    auth/
      api/
        auth_api.dart
        auth_dto.dart
      data/
        auth_repository.dart
      domain/
        user.dart
        auth_rules.dart
      state/
        auth_controller.dart
        auth_state.dart
      ui/
        login_page.dart
        widgets/
          login_form.dart
      test_support/
        auth_fakes.dart
    profile/
      api/
      data/
      domain/
      state/
      ui/
  main.dart
test/
  features/
    auth/
      auth_flow_test.dart
  shared/
    utils/
      result_test.dart

Ova struktura nije “manje disciplinirana”. Samo je disciplinirana oko featurea, a ne oko globalnih slojeva.

Granice ovisnosti za Feature-First#

Feature-First propada kada featurei slobodno importaju jedni druge. Riješite to definiranjem eksplicitno dopuštenih ovisnosti.

PodručjeSmije ovisiti oNe bi trebalo ovisiti o
features/*shared/* i isti featureDirektno o drugim featureima
shared/*Dart core, vanjski SDK-oviBilo koji features/*
app/*Featurei i sharedNišta izvan lib/

Komunikaciju između featurea možete dopustiti kroz uske “seamove”:

  • Routing sloj koji prosljeđuje samo primitive ili ID-eve.
  • Zajednički domenski ugovor u shared/ ili zaseban paket.
  • Evente preko analytics ili messaging sučelja.

ℹ️ Napomena: Ako vam trebaju česti importovi feature-to-feature, to obično znači da vam nedostaje shared apstrakcija ili da granica featurea ne odgovara proizvodu.

Pravila modeliranja podataka u Feature-First#

Feature-First najbolje radi kada svaki feature “posjeduje” svoje modele i mapira ih na granici.

  • Držite API DTO-e u features/feature/api.
  • Držite sučelja repozitorija i lokalnu data logiku u features/feature/data.
  • Držite poslovna pravila i stabilne entitete u features/feature/domain.

Time sprječavate “global models” mapu koja postaje odlagalište.

Primjer: Održavanje izolacije featurea#

Čest slučaj je da profile treba trenutnog korisnika iz autha. Umjesto da importate auth interne detalje, izložite minimalan ugovor.

Opcija A: definirajte shared sučelje u shared/.

Dart
// lib/shared/session/session_reader.dart
abstract class SessionReader {
  String? get currentUserId;
}

Auth ga implementira interno, a Profile ovisi samo o sučelju. DI wiring veže implementaciju.

Opcija B: proslijedite userId kroz navigaciju i dohvatite profil po ID-u. Time zadržavate nizak coupling i olakšavate deep linkove.

Strategija testiranja za Feature-First#

Testiranje u Feature-First najefikasnije je kada svaki feature ima vlastite test assete i fakes, a integracijske testove rezervirate za tokove kroz više featurea.

Tip testaGdje se nalaziFokus
Unit testovifeatures/*/domainPravila, mapperi, rubni slučajevi
Widget testovifeatures/*/uiStanja ekrana i renderiranje
Integracijski testovitest/ rootKritični putovi kroz više featurea
Contract testoviFeature API slojStabilnost API shapea i obrada grešaka

Contract testovi su važni kada često isporučujete, a backend timovi deployaju neovisno. Ako koristite Firebase, problemi stabilnosti se i dalje mogu dogoditi zbog security rules, indeksa i evolucije sheme, pa pomaže rano validirati reads i writes. Pogledajte production uzorke u Flutter Firebase tutorial.

💡 Savjet: Za testove featurea, kad god je moguće, preferirajte fakes umjesto mockova. Fakes smanjuju krhka očekivanja i ponašaju se bliže production kodu.

# Clean Architecture vs Feature-First: odabir prema timu i ritmu izdanja#

Najjednostavniji okvir odluke: optimizirajte prema svojim ograničenjima.

Matrica odluke#

Vaše ograničenjeBolji defaultZašto
Veličina tima 1 do 3Feature-FirstNizak overhead, najbrža iteracija
Veličina tima 4 do 8Feature-First sa strogim granicamaOwnership po featureu, upravljiva složenost
Veličina tima 9+Clean Architecture ili hibridJača separacija smanjuje trošak koordinacije
Ritm izdanja tjedno ili bržeFeature-FirstKratke povratne petlje i vertikalni sliceovi
Ritm izdanja mjesečno, težak QAClean ArchitectureIzolacija testova smanjuje rizik regresija
Složenost domene visokaClean ArchitectureŠtiti domenu od framework churn-a
Dugoročno održavanje 2+ godineClean Architecture ili hibridBolja sigurnost refaktora i onboarding

Praktičan hibrid koji dobro radi#

Mnoge uspješne aplikacije kombiniraju oba pristupa:

  • Feature-First na top razini: features/auth, features/profile.
  • Clean Architecture unutar svakog featurea: data, domain, presentation ili ui/state.

Time se izbjegava “global layered monolith”, a zadržavaju se stroge granice.

Hibridna struktura izgleda ovako:

Text
lib/
  shared/
    network/
    storage/
    ui/
  features/
    checkout/
      data/
      domain/
      presentation/
    catalog/
      data/
      domain/
      presentation/
  app/
    router.dart
    di.dart

To je često najbolja polazna točka za timove od 4 do 10 koji isporučuju svakih 1 do 2 tjedna.

Što standardizirati kako biste izbjegli “architecture drift”#

Arhitektura se skalira samo ako ju je lako pratiti i teško prekršiti.

Standardizirajte ove tri stvari:

  1. 1
    Smjer ovisnosti: dokumentirajte dopuštene importove i provodite kroz code review.
  2. 2
    Konvencije imenovanja: konzistentno dto, entity, repository, controller.
  3. 3
    Ugovore testiranja: minimalni skup testova potreban da se feature smatra završenim.

Ako standardizirate i granice statea, smanjujete churn kada se zahtjevi mijenjaju. Za moderne obrasce i trade-offe koristite naš referentni članak Flutter state management u 2026.

⚠️ Upozorenje: “Refaktorirat ćemo kasnije” postaje skupo nakon trećeg ili četvrtog featurea izgrađenog na pogrešnim pretpostavkama. Ako već osjećate bol, zamrznite razvoj featurea na jedan sprint i riješite probleme s granicama prije nego se umnože.

# Checklist za implementaciju: kako skalirati bilo koju arhitekturu#

Ovo su konkretne prakse koje oba pristupa održavaju zdravima.

1) Provedite import pravila#

Napravite jednostavan set arhitekturnih pravila u README-u i učinite ih provjerljivima u reviewu. Zatim rušite build kada se pravila krše.

Lagani pristup je skripta koja u CI-u grep-a zabranjene importove.

Bash
#!/usr/bin/env bash
set -e
 
# Example: prevent cross-feature imports
if rg "import 'package:.*features/.*/" lib/features -g'*.dart' | rg -v "features/\1"; then
  echo "Cross-feature imports detected. Use shared contracts or routing."
  exit 1
fi

Neka bude jednostavno. Kasnije možete prijeći na strože alate.

2) Držite shared kod malenim i namjernim#

Ako sve postane shared, nitko ne “posjeduje” ništa.

Dobri kandidati za shared/:

Dobar shared kandidatZašto pripada u shared
Wrapper za API klijentaCross-cutting concern
Logging i analyticsCross-cutting concern
UI komponenteKoriste se u više featurea
Result i error tipoviStandardiziraju obradu failurea

Loši kandidati za shared/:

Loš shared kandidatZašto stvara probleme
Modeli specifični za featureCoupling i nejasan ownership
Servisi specifični za featureSkriveni dependencyji
“utils” odlagališteTeško za pronaći, lako za zloupotrijebiti

3) Koristite stabilnu obradu grešaka kroz slojeve#

Rano odaberite konzistentnu strategiju grešaka. Na primjer:

  • Data sloj interno baca exceptions.
  • Domain sloj izlaže failures kao tipizirani rezultat.
  • Presentation mapira failure u korisničke poruke.

Zapišite formulu za tim na jednom mjestu i držite je konzistentnom.

4) Uložite u CI od početka#

Skalabilna arhitektura bez CI-a i dalje puca pod brzinom.

Minimalne CI provjere koje se brzo isplate:

  • flutter analyze
  • unit testovi
  • mali set widget testova
  • provedba formatiranja

# Ključne poruke#

  • Koristite Clean Architecture kada složenost domene, rizik i dugoročno održavanje nadmašuju kratkoročnu brzinu isporuke.
  • Koristite Feature-First kada često isporučujete i želite brzu iteraciju, ali provedite stroge granice kako biste izbjegli coupling između featurea.
  • Držite modele odvojenima: Entities u domainu, DTO-i u data sloju i UI modeli u presentationu kako biste smanjili cijenu promjena.
  • Usvojite praktičnu piramidu testiranja: unit testovi za pravila, widget testovi za UI stanja i malo integracijskih testova za kritične funnel-e.
  • Hibrid je često najbolji: Feature-First na top razini i Clean slojevi unutar svakog featurea.

# Zaključak#

Skalabilna arhitektura Flutter aplikacije manje je stvar mapa, a više provedbe smjera ovisnosti, držanja modela tamo gdje pripadaju i testiranja na pravim granicama. Ako odaberete Clean Architecture, kupujete dugoročnu sigurnost refaktora. Ako odaberete Feature-First, kupujete brzinu iteracije i jasniji ownership—pod uvjetom da spriječite importove između featurea.

Ako želite stručni pregled vaše trenutne strukture ili pomoć pri postavljanju production-ready temelja s CI-jem, testiranjem i automatizacijom izdanja, kontaktirajte Samioda i napravit ćemo audit vašeg Flutter codebasea te predložiti konkretan plan migracije vezan uz vaš roadmap i ritam izdanja.

FAQ

Share
A
Adrijan OmičevićSamioda Team
All articles →

Trebate pomoć s projektom?

Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.