Modulariteit is het principe waarbij een applicatie wordt opgebouwd uit onafhankelijke modules, elk verantwoordelijk voor één functionaliteit. Volgens Android Developers versnelt de opsplitsing in modules de build door parallelle compilatie en stelt het teams in staat om onafhankelijk aan verschillende delen van de applicatie te werken. Modulaire architectuur is de standaard geworden voor grote mobiele projecten met tientallen ontwikkelaars.
Belangrijkste
Modulariteit is een manier van code-organisatie waarbij een applicatie bestaat uit losjes gekoppelde modules, die elk een strikt gedefinieerde functionaliteit bieden via een publieke interface. In tegenstelling tot monolithische architectuur, waar alle klassen in één project zitten, verdeelt de modulaire benadering de code in fysiek onafhankelijke compileereenheden.
Het hoofddoel van modulariteit is het beheersen van complexiteit. Een ontwikkelaar kan zich op één module concentreren zonder de hele codebase in gedachten te houden. Elke module heeft zijn eigen verantwoordelijkheidsgebied en kan onafhankelijk van de rest worden ontwikkeld, getest en geïmplementeerd. Dit is vooral waardevol bij projecten met 10+ ontwikkelaars, waar parallel werk aan een monoliet leidt tot frequente samenvoegingsconflicten.
Het is belangrijk om modulariteit te onderscheiden van gelaagde architectuur. Lagen (Presentation, Domain, Data) verdelen code op technisch criterium, terwijl modules op functioneel criterium verdelen. De module “Gebruikersprofiel” kan zijn eigen lagen bevatten. In de praktijk worden modulaire benadering en gelaagde architectuur gecombineerd: elke module heeft zijn eigen drielagige structuur.
Feature-modules — het populairste type modules. Elk scherm of groep gerelateerde schermen wordt in een aparte module ondergebracht: Onboarding, Profile, Settings, Feed. Een feature-module bevat alles wat nodig is voor de werking van de functie: UI, bedrijfslogica, gegevenslaag. De grenzen van de module zijn beschermd — andere functies hebben geen toegang tot de interne klassen.
Core-modules bevatten de gemeenschappelijke infrastructuur: netwerkwerk, database, analyse, designsysteem. Ze zijn niet afhankelijk van feature-modules, maar feature-modules zijn wel afhankelijk van hen. Deze verdeling garandeert dat wijziging van de analyse-SDK geen invloed heeft op de netwerklaag en vice versa. Core-modules worden herbruikt tussen functies zonder codeduplicatie.
Shared-modules bevatten code die door meerdere functies wordt gebruikt: gegevensmodellen, hulpprogramma's, constanten, aangepaste views. Het grootste probleem van shared-modules is het risico om een stortplaats (“misc module”) te worden, waar na verloop van tijd diverse code zich ophoopt. Regel: een shared-module moet een duidelijk thema hebben, bijvoorbeeld “shared-ui” of “shared-models”.
In Android worden shared-modules vaak ondergebracht in bibliotheken met het voorvoegsel lib: lib-network, lib-database, lib-ui-components. In iOS vervullen interne Swift Packages binnen de Workspace dezelfde functies. In de praktijk beperken teams het aantal shared-modules tot 3–5 om een overmatig netwerk van afhankelijkheden te voorkomen dat de build compliceert.
Aparte testmodules maken het mogelijk om tests alleen voor de gewijzigde module uit te voeren, zonder de hele testbasis te draaien. Dit verkort de CI/CD-pipeline van uren naar minuten. Modules zorgen voor scheiding op build-niveau: de module van de netwerklaag kan niet per ongeluk UI-bibliotheken importeren in tests.
Elke module moet een duidelijk gedefinieerde publieke API hebben. In Android wordt dit bereikt via toegangsmodifiers en api vs implementation in Gradle. In iOS — via public/internal toegangsmodifiers en beheerde afhankelijkheden via Package.swift. Het minimaliseren van zichtbaarheid tot het noodzakelijke is een belangrijke praktijk van modulair ontwerp.
Gradle ondersteunt modulaire architectuur native: elke module is een aparte compileereenheid met zijn eigen build.gradle-bestand. Android-projecten gebruiken een combinatie van een application-module (app) en meerdere library-modules. Bibliotheekmodules kunnen niet als applicatie worden gestart, maar kunnen als AAR in de repository worden gepubliceerd.
De belangrijkste functie van Gradle is parallelle build van onafhankelijke modules. Als modules A, B en C niet van elkaar afhankelijk zijn, compileert Gradle ze gelijktijdig, gebruikmakend van alle processorkernen. Bij projecten met 20+ modules verkort dit de volledige build van 15 naar 3–5 minuten. Incrementele build van een gewijzigde module duurt seconden.
Gradle biedt twee soorten afhankelijkheden tussen modules: api (transitief) en implementation (niet-transitief). Het verschil is cruciaal voor modulariteit: implementation verbergt transitieve afhankelijkheden voor consumenten van de module. Als module :profile :networking via implementation gebruikt, weten consumenten van :profile niet van :networking en kunnen ze er niet naar verwijzen.
// settings.gradle — declaratie van modules
include ':app'
include ':feature:profile'
include ':feature:settings'
include ':core:network'
include ':core:database'
// build.gradle feature/profile — module-afhankelijkheden
dependencies {
implementation project(':core:network')
implementation project(':core:database')
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
}
De code toont de structuur van een modulair Android-project. Settings.gradle somt alle modules op, en build.gradle van elke feature-module geeft alleen de core-modules aan die nodig zijn. Het buildsysteem lost automatisch transitieve afhankelijkheden op en compileert modules in de juiste volgorde.
Swift Package Manager (SPM) — de standaardtool voor modulariteit in iOS sinds 2019. SPM maakt het mogelijk om een applicatie op te splitsen in Swift Packages, die elk een library of executable kunnen zijn. Package definieert modules (targets) en hun afhankelijkheden via Package.swift. SPM is geïntegreerd in Xcode en vereist geen extra tools.
CocoaPods blijft de belangrijkste afhankelijkheidsbeheerder voor bibliotheken van derden. Podfile en Podspec definiëren de modulaire structuur, en CocoaPods genereert een workspace met aparte pod-projecten. Voor de eigen modulariteit van het project kiezen teams steeds vaker voor SPM, omdat het in Xcode is ingebouwd en geen installatie vereist.
In iOS-modulariteit speelt toegangscontrole een belangrijke rol: public, package, internal, fileprivate en private. Een module publiceert alleen de types die toegankelijk moeten zijn voor andere modules. Interne implementatiedetails zijn verborgen achter internal en private modifiers. Dit voorkomt verborgen afhankelijkheden tussen modules.
// Package.swift — modulaire structuur van iOS-project
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 declareert twee library-producten: ProfileFeature en NetworkCore. ProfileFeature is afhankelijk van NetworkCore, maar weet niet van het bestaan van Alamofire — die is verborgen in NetworkCore. Een dergelijke isolatie is een directe toepassing van scheiding op modulair niveau: wijzigingen in de HTTP-client vereisen geen hercompilatie van ProfileFeature.
Het belangrijkste voordeel van modulariteit is ontwikkelingssnelheid. Teams werken parallel aan verschillende modules zonder codeconflicten. De CI/CD-pipeline bouwt alleen gewijzigde modules en voert alleen hun tests uit. De feedbacktijd wordt korter en de releasefrequentie neemt toe. Spotify, Uber en Airbnb hebben migraties naar modulaire architectuur gepubliceerd met een verbetering van metrieken met 2–3 keer.
Het tweede voordeel — foutisolatie. Een bug in de Profile-module heeft geen invloed op de Payments-module, als er geen directe afhankelijkheden tussen zijn. Dit is vooral belangrijk in applicaties met functies met een hoog risico (betalingen, medische gegevens), waar een fout in een niet-gerelateerd scherm de release van kritieke functionaliteit niet mag blokkeren.
De grootste uitdaging — afhankelijkheidsbeheer. Bij onjuist ontwerp ontstaat een graaf van modules waarbij wijziging van één module tientallen andere modules cascadeert. Oplossing — volg de regel van acycliciteit: de afhankelijkheidsgraaf van modules moet een gerichte acyclische graaf (DAG) zijn. Tools zoals Gradle Module Graph Assert helpen bij het detecteren van cycli tijdens de build.
De tweede uitdaging — langere initiële configuratietijd. Het creëren van een modulaire architectuur kost meer tijd in de initiatiefase van een project. Kleine projecten met 1–3 ontwikkelaars hebben mogelijk geen baat bij modulariteit, omdat ze tijd besteden aan het onderhouden van modulegrenzen zonder echte behoefte aan parallellisatie. Oplossing — begin met een monoliet en extraheer modules naarmate het team groeit.
Feature-first benadering groepeert modules op functionaliteit: elk scherm of groep schermen wordt een aparte module. Layer-first benadering verdeelt code op technisch criterium: aparte modules voor UI, bedrijfslogica en gegevens. In de praktijk kiezen de meeste teams voor feature-first met core-modules — dit geeft betere isolatie en duidelijke navigatie door het project.
De keuze tussen benaderingen hangt af van de teamgrootte en voorspelbaarheid van functionaliteit. Als je precies weet welke schermen in het project komen, stelt feature-first elke ontwikkelaar in staat om verantwoordelijk te zijn voor zijn eigen module. Als functionaliteit vaak verandert en overlapt tussen schermen, biedt layer-first meer flexibiliteit bij het herbruiken van code tussen verschillende functies.
Veelgestelde vragen
Het optimale aantal hangt af van de projectgrootte en het team. Voor een team van 5 personen zijn 6–10 modules voldoende. Voor 20+ ontwikkelaars — 20–40 modules. Regel: een module moet klein genoeg zijn zodat één ontwikkelaar hem volledig begrijpt, en groot genoeg om geen overmatig netwerk van afhankelijkheden te creëren.
Correcte modulariteit versnelt de build door parallelle compilatie en caching. Maar een overmatig aantal modules met dichte afhankelijkheden vertraagt de build — Gradle en Xcode besteden tijd aan het oplossen van de graaf. De sleutel tot een snelle build — minimaliseer transitieve afhankelijkheden en respecteer acycliciteit.
Ja, maar iteratief. Begin met het extraheren van core-modules (netwerk, database), extraheer vervolgens functies één voor één. Gebruik feature flags om nieuwe modulaire code parallel met oude monolithische code in te schakelen. Volledige migratie van een grote applicatie duurt 3 tot 12 maanden.
Modules zijn compileereenheden binnen één applicatie. Microservices zijn afzonderlijke processen die op verschillende servers draaien. Modules verdelen code, microservices verdelen runtime. In mobiele ontwikkeling wordt vaak de term “microapps” gebruikt als hybride: feature-modules die als zelfstandige applicaties kunnen worden gestart.
Elke module heeft zijn eigen unittesten, onafhankelijk uitvoerbaar. Integratietests controleren de interactie tussen modules. UI-tests dekken feature-modules met mockgegevens. Modulaire architectuur vereenvoudigt testen: het mocken van een afhankelijkheid van een andere module is gemakkelijker dan het mocken van een deel van een monoliet.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook