Modularitatea este principiul prin care aplicația este construită din module independente, fiecare responsabil pentru o funcționalitate. Potrivit Android Developers, împărțirea în module accelerează compilarea prin compilare paralelă și permite echipelor să lucreze la diferite părți ale aplicației independent. Arhitectura modulară a devenit standardul pentru proiectele mobile mari cu zeci de dezvoltatori.
Principalele
Modularitatea este un mod de organizare a codului în care aplicația constă din module slab cuplate, fiecare oferind o funcționalitate strict definită printr-o interfață publică. Spre deosebire de arhitectura monolitică, unde toate clasele se află într-un singur proiect, abordarea modulară împarte codul în unități de compilare fizic independente.
Scopul principal al modularității este gestionarea complexității. Dezvoltatorul se poate concentra pe un singur modul fără a ține în minte întreaga bază de cod. Fiecare modul are propria sa zonă de responsabilitate și poate fi dezvoltat, testat și implementat independent de celelalte. Acest lucru este deosebit de valoros în proiectele cu 10+ dezvoltatori, unde munca paralelă pe un monolit duce la conflicte frecvente de îmbinare.
Este important să distingem modularitatea de arhitectura stratificată. Straturile (Presentation, Domain, Data) împart codul după criteriul tehnic, iar modulele — după cel funcțional. Modulul „Profil utilizator” poate conține propriile straturi în interiorul său. În practică, abordarea modulară și arhitectura stratificată se combină: fiecare modul are propria sa structură pe trei straturi.
Modulele Feature — cel mai popular tip de module. Fiecare ecran sau grup de ecrane înrudite este separat într-un modul propriu: Onboarding, Profile, Settings, Feed. Modulul Feature conține tot ce este necesar pentru funcționarea respectivei funcționalități: UI, logică de business, stratul de date. Granițele modulului sunt protejate — alte funcționalități nu pot accesa clasele sale interne.
Modulele Core conțin infrastructura comună: lucrul cu rețeaua, baza de date, analitica, sistemul de design. Ele nu depind de modulele Feature, dar modulele Feature depind de ele. Această împărțire garantează că modificarea SDK-ului de analitică nu va afecta stratul de rețea și invers. Modulele Core sunt reutilizate între funcționalități fără duplicare de cod.
Modulele Shared conțin cod utilizat de mai multe funcționalități: modele de date, utilități, constante, View-uri personalizate. Problema principală a modulelor Shared este riscul de a se transforma într-o groapă de gunoi („misc module”), unde se acumulează cod eterogen în timp. Regula: modulul Shared trebuie să aibă o tematică clară, de exemplu „shared-ui” sau „shared-models”.
În Android, modulele Shared sunt adesea separate în biblioteci cu prefixul lib: lib-network, lib-database, lib-ui-components. În iOS, aceleași funcții sunt îndeplinite de Swift Packages interne în cadrul Workspace. În practică, echipele limitează numărul modulelor Shared la 3–5 pentru a nu crea o rețea excesivă de dependențe care complică compilarea.
Modulele de test separate permit rularea testelor doar pentru modulul modificat, fără a rula întreaga bază de teste. Acest lucru reduce timpul pipeline-ului CI/CD de la ore la minute. Modulele asigură separarea la nivel de compilare: modulul stratului de rețea nu poate importa accidental biblioteci UI în teste.
Fiecare modul trebuie să aibă o API publică clar definită. În Android, acest lucru se realizează prin modificatori de acces și api vs implementation în Gradle. În iOS — prin modificatorii de acces public/internal și dependențe gestionate prin Package.swift. Reducerea vizibilității la minimul necesar este o practică cheie a designului modular.
Gradle suportă nativ arhitectura modulară: fiecare modul este o unitate de compilare separată cu propriul fișier build.gradle. Proiectele Android utilizează o combinație a modulului application (app) și a mai multor module library. Modulele bibliotecă nu pot fi rulate ca aplicație, dar pot fi publicate ca AAR în repository.
Funcția cheie a Gradle este compilarea paralelă a modulelor independente. Dacă modulele A, B și C nu depind unul de celălalt, Gradle le compilează simultan, folosind toate nucleele procesorului. În proiectele cu 20+ module, acest lucru reduce compilarea completă de la 15 la 3–5 minute. Compilarea incrementală a modulului modificat durează secunde.
Gradle oferă două tipuri de dependențe între module: api (tranzitive) și implementation (netranzitive). Diferența este critică pentru modularitate: implementation ascunde dependențele tranzitive de consumatorii modulului. Dacă modulul :profile utilizează :networking prin implementation, consumatorii :profile nu știu de :networking și nu pot apela la el.
// settings.gradle — declararea modulelor
include ':app'
include ':feature:profile'
include ':feature:settings'
include ':core:network'
include ':core:database'
// build.gradle feature/profile — dependențele modulului
dependencies {
implementation project(':core:network')
implementation project(':core:database')
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
}
Codul arată structura unui proiect Android modular. Settings.gradle enumeră toate modulele, iar build.gradle al fiecărui modul Feature indică doar modulele Core de care are nevoie. Sistemul de compilare rezolvă automat dependențele tranzitive și compilează modulele în ordinea corectă.
Swift Package Manager (SPM) — instrumentul standard de modularitate în iOS din 2019. SPM permite împărțirea aplicației în Swift Packages, fiecare putând fi library sau executable. Package definește modulele (targets) și dependențele lor prin Package.swift. SPM este integrat în Xcode și nu necesită instrumente suplimentare.
CocoaPods rămâne principalul manager de dependențe pentru bibliotecile terțe. Podfile și Podspec definesc structura modulară, iar CocoaPods generează un workspace cu proiecte pod separate. Pentru modularitatea proprie a proiectului, echipele aleg din ce în ce mai des SPM, deoarece este încorporat în Xcode și nu necesită instalare.
În modularitatea iOS, controlul accesului joacă un rol important: public, package, internal, fileprivate și private. Modulul publică doar tipurile care trebuie să fie accesibile altor module. Detaliile interne de implementare sunt ascunse în spatele modificatorilor internal și private. Acest lucru previne apariția dependențelor ascunse între module.
// Package.swift — structura modulară a proiectului iOS
let package = Package(
name: "MyApp",
platforms: [.iOS(SupportedPlatform.iOSVersion.v17)],
products: [
.library(name: "ProfileFeature", targets: ["ProfileFeature"]),
.library(name: "NetworkCore", targets: ["NetworkCore"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
],
targets: [
.target(name: "ProfileFeature", dependencies: ["NetworkCore"]),
.target(name: "NetworkCore", dependencies: ["Alamofire"]),
]
)
Package.swift declară două produse de bibliotecă: ProfileFeature și NetworkCore. ProfileFeature depinde de NetworkCore, dar nu știe de existența Alamofire — acesta este ascuns în interiorul NetworkCore. O astfel de izolare este aplicarea directă a separării la nivel de module: modificările în clientul HTTP nu necesită recompilarea ProfileFeature.
Principalul avantaj al modularității este viteza de dezvoltare. Echipele lucrează paralel pe diferite module fără conflicte în cod. Pipeline-ul CI/CD compilează doar modulele modificate și rulează doar testele lor. Timpul de feedback se reduce, iar frecvența lansărilor crește. Spotify, Uber și Airbnb au publicat cazuri de migrare la arhitectura modulară cu îmbunătățirea metricilor de 2–3 ori.
Al doilea avantaj — izolarea erorilor. O eroare în modulul Profile nu afectează modulul Payments, dacă nu există dependențe directe între ele. Acest lucru este deosebit de important în aplicațiile cu funcționalități de risc ridicat (plăți, date medicale), unde o eroare într-un ecran neconex nu trebuie să blocheze lansarea funcționalității critice.
Principala provocare — gestionarea dependențelor. La o proiectare incorectă, apare un graf de module în care modificarea unui modul reconstruiește în cascadă zeci de altele. Soluția — respectarea regulii de aciclicitate: graful de dependențe al modulelor trebuie să fie un graf aciclic dirijat (DAG). Instrumente precum Gradle Module Graph Assert ajută la detectarea ciclurilor în faza de compilare.
A doua provocare — creșterea timpului de configurare inițială. Crearea unei arhitecturi modulare necesită mai mult timp în faza de inițializare a proiectului. Proiectele mici cu 1–3 dezvoltatori pot să nu beneficieze de modularitate, pierzând timp cu menținerea granițelor modulelor fără o nevoie reală de paralelizare. Soluția — începe cu un monolit și extrage module pe măsură ce echipa crește.
Abordarea Feature-first grupează modulele după funcționalitate: fiecare ecran sau grup de ecrane devine un modul separat. Abordarea Layer-first împarte codul după criteriul tehnic: module separate pentru UI, logică de business și date. În practică, majoritatea echipelor aleg feature-first cu module Core — aceasta oferă o izolare mai bună și o navigare clară în proiect.
Alegerea între abordări depinde de dimensiunea echipei și predictibilitatea funcționalităților. Dacă știți exact ce ecrane vor fi în proiect, feature-first permite fiecărui dezvoltator să fie responsabil pentru propriul modul. Dacă funcționalitățile se schimbă frecvent și se intersectează între ecrane, layer-first oferă mai multă flexibilitate la reutilizarea codului între diferite funcționalități.
Întrebări frecvente
Numărul optim depinde de dimensiunea proiectului și a echipei. Pentru o echipă de 5 persoane sunt suficiente 6–10 module. Pentru 20+ dezvoltatori — 20–40 module. Regula: modulul trebuie să fie suficient de mic pentru ca un singur dezvoltator să îl înțeleagă în întregime și suficient de mare pentru a nu crea o rețea excesivă de dependențe.
Modularitatea corectă accelerează compilarea prin compilare paralelă și cache. Dar un număr excesiv de module cu dependențe dense încetinește compilarea — Gradle și Xcode petrec timp rezolvând graful. Cheia compilării rapide — minimizarea dependențelor tranzitive și respectarea aciclicității.
Da, dar iterativ. Începeți prin extragerea modulelor Core (rețea, bază de date), apoi extrageți funcționalitățile una câte una. Utilizați feature flags pentru a activa noul cod modular în paralel cu cel monolitic vechi. Migrarea completă a unei aplicații mari durează între 3 și 12 luni.
Modulele sunt unități de compilare în cadrul unei singure aplicații. Microserviciile sunt procese separate care rulează pe servere diferite. Modulele împart codul, microserviciile împart runtime-ul. În dezvoltarea mobilă, se folosește adesea termenul „microapps” ca hibrid: module Feature care pot rula ca aplicații independente.
Fiecare modul are propriile teste unitare, rulabile independent. Testele de integrare verifică interacțiunea între module. Testele UI acoperă modulele Feature cu date mock. Arhitectura modulară simplifică testarea: mock-ul unei dependențe a altui modul este mai ușor decât mock-ul unei părți dintr-un monolit.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și