4 Projektstruktur und Konventionen

4.1 Convention over Configuration in Gradle

Gradle folgt dem Prinzip “Convention over Configuration”, das ursprünglich durch Ruby on Rails popularisiert wurde. Dieses Konzept bedeutet, dass Gradle sinnvolle Standardwerte für alle Aspekte eines Builds vorgibt, diese aber bei Bedarf überschrieben werden können. Die Konventionen reduzieren die notwendige Konfiguration erheblich und sorgen für einheitliche Projektstrukturen über verschiedene Teams und Projekte hinweg.

Die Konventionen werden durch Plugins eingeführt. Das Java-Plugin beispielsweise definiert, dass Java-Quellcode unter src/main/java liegt, kompilierte Klassen nach build/classes/java/main geschrieben werden und das finale JAR-Archiv als build/libs/projektname-version.jar erstellt wird. Diese Konventionen basieren auf jahrelanger Erfahrung der Java-Community und haben sich als Best Practices etabliert.

4.1.1 Standard-Konventionen des Java-Plugins

Aspekt Standard-Konvention Überschreibbar via
Source Code src/main/java sourceSets.main.java.srcDirs
Test Code src/test/java sourceSets.test.java.srcDirs
Ressourcen src/main/resources sourceSets.main.resources.srcDirs
Test-Ressourcen src/test/resources sourceSets.test.resources.srcDirs
Kompilierte Klassen build/classes/java/main sourceSets.main.output.classesDirs
Test-Klassen build/classes/java/test sourceSets.test.output.classesDirs
JAR-Datei build/libs/${project.name}-${version}.jar jar.archiveFileName
Test-Reports build/reports/tests test.reports.html.outputLocation
JavaDoc build/docs/javadoc javadoc.destinationDir

Der Vorteil dieses Ansatzes zeigt sich besonders beim Onboarding neuer Teammitglieder. Ein Entwickler, der Gradle kennt, findet sich sofort in jedem Gradle-Projekt zurecht, das den Konventionen folgt. Die kognitive Last reduziert sich, da nicht für jedes Projekt neue Strukturen erlernt werden müssen. Gleichzeitig bleibt die Flexibilität erhalten, spezielle Anforderungen durch Konfiguration zu erfüllen.

4.2 Source Sets und ihre Verwaltung

Source Sets sind ein zentrales Konzept in Gradle zur Organisation von Quellcode und Ressourcen. Ein Source Set definiert eine logische Gruppierung von Sourcecode-Verzeichnissen, Ressourcen-Verzeichnissen und spezifischen Classpaths. Das Java-Plugin konfiguriert standardmäßig zwei Source Sets: main für Produktivcode und test für Testcode.

Jedes Source Set verfügt über separate Compile- und Runtime-Classpaths. Der main Source Set nutzt die implementation und compileOnly Dependencies, während der test Source Set zusätzlich auf die kompilierten Klassen und Ressourcen von main zugreift. Diese Trennung verhindert, dass Testabhängigkeiten wie JUnit in Produktivcode verwendet werden können.

Zusätzliche Source Sets werden für spezielle Anforderungen definiert. Ein typisches Beispiel ist ein integrationTest Source Set für Integrationstests:

sourceSets {
    create("integrationTest") {
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
        java.srcDir("src/integrationTest/java")
        resources.srcDir("src/integrationTest/resources")
    }
}

configurations["integrationTestImplementation"].extendsFrom(configurations.implementation.get())
configurations["integrationTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())

Diese Konfiguration erstellt eine separate Testumgebung mit eigenen Abhängigkeiten und Ressourcen, die unabhängig von Unit-Tests ausgeführt werden kann.

4.2.1 Übliche Custom Source Sets

Source Set Name Verwendungszweck Typische Dependencies Ausführung
integrationTest Integrationstests mit externen Systemen TestContainers, REST-assured, WireMock Nach Unit-Tests
functionalTest End-to-End Tests der Anwendung Selenium, Cucumber, Appium Nach Integration Tests
performanceTest Last- und Performance-Tests JMeter, Gatling, Apache Bench Manuell/CI-Pipeline
contractTest API Contract Tests Pact, Spring Cloud Contract Parallel zu Unit-Tests
smokeTest Minimale Validierung nach Deployment REST-assured, Minimal-Set Nach Deployment

4.3 Verzeichnisstruktur für verschiedene Projekttypen

4.3.1 Projekttypen-Übersicht

Projekttyp Hauptplugin Charakteristische Verzeichnisse Besonderheit
Java-Bibliothek java-library src/main/java, src/test/java Standard-Struktur
Java-Anwendung application Standard + Main-Class Ausführbare JAR
Web-Anwendung war src/main/webapp WAR-Deployment
Spring Boot spring-boot src/main/resources/application.yml Embedded Server
Kotlin kotlin src/main/kotlin Parallel zu Java
Android com.android.application src/main/res, AndroidManifest.xml Varianten-Struktur

Die Projektstruktur variiert je nach verwendeten Plugins und Projekttyp. Ein Standard-Java-Bibliotheksprojekt folgt der bereits beschriebenen Struktur mit src/main und src/test. Webanwendungen, die das War-Plugin verwenden, erweitern diese Struktur um src/main/webapp für statische Web-Ressourcen wie HTML, CSS und JavaScript-Dateien.

Kotlin-Projekte nutzen parallele Verzeichnisse zu Java. Der Kotlin-Code liegt in src/main/kotlin und src/test/kotlin, kann aber problemlos mit Java-Code in den entsprechenden Java-Verzeichnissen koexistieren. Diese Struktur ermöglicht eine graduelle Migration von Java zu Kotlin oder die Verwendung beider Sprachen im selben Projekt.

Android-Projekte weichen stärker von der Standard-Struktur ab. Sie verwenden src/main/java für Code, aber auch src/main/res für Android-Ressourcen und src/main/AndroidManifest.xml für die Manifest-Datei. Build-Varianten wie debug und release erhalten eigene Verzeichnisse unter src/debug und src/release. Diese Struktur reflektiert die spezifischen Anforderungen der Android-Plattform.

Spring Boot Projekte folgen der Standard-Java-Struktur, ergänzen diese aber häufig um src/main/resources/application.properties oder application.yml für die Konfiguration. Die static und templates Verzeichnisse unter src/main/resources enthalten statische Ressourcen und View-Templates für Webanwendungen.

4.4 Build-Verzeichnis und generierte Artefakte

Das build Verzeichnis ist der zentrale Ort für alle generierten Dateien. Gradle strukturiert dieses Verzeichnis systematisch, um verschiedene Arten von Output zu organisieren. Unter build/classes liegen die kompilierten Klassen, getrennt nach Sprache und Source Set. Das Verzeichnis build/generated enthält generierten Sourcecode, beispielsweise von Annotation Processors oder Code-Generatoren.

4.4.1 Struktur des Build-Verzeichnisses

Verzeichnis Inhalt Beispiele
build/classes Kompilierte Klassen java/main/, java/test/, kotlin/main/
build/resources Verarbeitete Ressourcen main/, test/
build/generated Generierter Code sources/annotationProcessor/
build/libs Finale Artefakte projekt-1.0.jar, projekt-1.0-sources.jar
build/reports Test- und Analyse-Reports tests/test/index.html, jacoco/
build/docs Dokumentation javadoc/, kdoc/
build/tmp Temporäre Dateien compileJava/, jar/, expandedArchives/
build/distributions Distributions-Archive projekt-1.0.zip, projekt-1.0.tar

Die Struktur build/reports sammelt verschiedene Berichte wie Test-Reports, Coverage-Reports oder Dependency-Reports. HTML-Test-Reports finden sich unter build/reports/tests/test/index.html und bieten eine detaillierte Übersicht über Testergebnisse. Das Verzeichnis build/libs enthält die finalen Artefakte wie JAR- oder WAR-Dateien.

Gradle nutzt build/tmp für temporäre Dateien während der Task-Ausführung. Jeder Task kann hier eigene temporäre Verzeichnisse anlegen. Das Verzeichnis build/resources spiegelt die verarbeiteten Ressourcen wider, nachdem Processing-Tasks wie das Ersetzen von Tokens ausgeführt wurden.

4.5 Multi-Modul-Projektstrukturen

Große Projekte profitieren von einer Aufteilung in mehrere Module. Ein typisches Multi-Modul-Projekt strukturiert sich hierarchisch mit einem Root-Projekt und mehreren Subprojekten. Die settings.gradle.kts im Root-Verzeichnis definiert die Projektstruktur:

rootProject.name = "enterprise-application"
include("core", "api", "web", "data-access")

Jedes Modul erhält ein eigenes Verzeichnis mit eigener build.gradle.kts Datei. Die Verzeichnisstruktur spiegelt die logische Architektur der Anwendung wider. Das core Modul enthält Domänenlogik, api definiert Schnittstellen, data-access kapselt Datenbankzugriffe und web implementiert die Präsentationsschicht.

Module können voneinander abhängen. Das web Modul deklariert eine Abhängigkeit zum core Modul mit implementation(project(":core")). Gradle stellt sicher, dass Module in der richtigen Reihenfolge gebaut werden und Änderungen in einem Modul automatisch abhängige Module triggern.

Gemeinsame Konfiguration wird im Root-Projekt zentralisiert. Die Root-build.gradle.kts kann Konfiguration auf alle oder spezifische Subprojekte anwenden:

subprojects {
    apply(plugin = "java")
    
    java {
        toolchain {
            languageVersion.set(JavaLanguageVersion.of(17))
        }
    }
    
    repositories {
        mavenCentral()
    }
}

4.6 Namenskonventionen und Best Practices

Gradle-Projekte folgen etablierten Namenskonventionen, die Konsistenz und Wartbarkeit fördern. Projektnamen verwenden Kleinbuchstaben mit Bindestrichen als Trennzeichen, beispielsweise customer-service oder payment-gateway. Diese Namen erscheinen in generierten Artefakten und sollten daher aussagekräftig und eindeutig sein.

4.6.1 Namenskonventionen im Überblick

Element Konvention Beispiele richtig Beispiele falsch
Projektnamen kebab-case customer-service, payment-api CustomerService, customer_service
Task-Namen camelCase generateDocs, deployToProduction generate-docs, deploy_to_production
Properties camelCase maxHeapSize, buildNumber max_heap_size, MaxHeapSize
Konstanten UPPER_SNAKE_CASE DEFAULT_TIMEOUT, MAX_CONNECTIONS defaultTimeout, max-connections
Verzeichnisse lowercase src, test, docs SRC, Test, DOCS
Konfigurationsdateien kebab-case mit Suffix application-dev.yml, gradle-wrapper.properties applicationDev.yml, gradle_wrapper.properties
Source Sets camelCase integrationTest, functionalTest integration-test, integration_test
Configurations camelCase compileOnly, testImplementation compile-only, test_implementation

Task-Namen folgen der camelCase-Konvention und beschreiben die ausgeführte Aktion. Verben wie compile, test, generate oder deploy kennzeichnen den Task-Zweck. Zusammengesetzte Task-Namen wie compileTestJava oder generateApiDocumentation verdeutlichen Kontext und Aktion.

Properties und Variablen in Build-Skripten nutzen ebenfalls camelCase. Konstanten werden in UPPER_SNAKE_CASE geschrieben. Extension-Properties für Projekt-Metadaten folgen der Punkt-Notation, etwa project.ext.apiVersion oder project.ext.dockerRegistry.

Verzeichnisnamen bleiben konsistent mit den Plugin-Konventionen. Eigene Verzeichnisse folgen dem gleichen Schema: Kleinbuchstaben, keine Sonderzeichen, aussagekräftige Namen. Ein Verzeichnis für Datenbankmigrationen heißt db-migrations, nicht DB_Migrations oder database_migration_scripts.

Die Trennung von Konfiguration und Konvention zeigt sich auch in der Benennung von Konfigurationsdateien. Umgebungsspezifische Properties nutzen das Schema application-{umgebung}.properties, also application-dev.properties oder application-prod.properties. Diese Konvention ermöglicht automatisches Laden basierend auf aktiven Profilen.