24 Gradle und Maven

24.1 Einführung

In der Java-Entwicklung sind Gradle und Maven die beiden führenden Build-Tools, die Entwicklern dabei helfen, Projekte zu automatisieren, zu verwalten und zu strukturieren. Beide Tools bieten Mechanismen zur Abhängigkeitsverwaltung, zum Bauen, Testen und Deployen von Anwendungen. Im Kontext der Contract-First-Entwicklung mit Spring Boot spielen Gradle und Maven eine zentrale Rolle, da sie die Integration von Tools wie dem OpenAPI Generator erleichtern und sicherstellen, dass der gesamte Entwicklungsprozess effizient und konsistent abläuft.

Dieses Kapitel bietet einen umfassenden Vergleich von Gradle und Maven, zeigt, wie man beide Tools in Spring Boot-Projekten konfiguriert und verwendet, insbesondere im Hinblick auf die Contract-First-Entwicklung. Zudem werden Best Practices vorgestellt, die helfen, die Vorteile beider Build-Tools optimal zu nutzen.

24.2 Grundlagen von Gradle und Maven

24.2.1 Maven

Maven ist ein etabliertes Build-Tool, das auf der XML-basierten Project Object Model (POM)-Konfiguration basiert. Es bietet eine standardisierte Struktur für Java-Projekte und legt großen Wert auf Konvention vor Konfiguration. Maven ist bekannt für seine umfangreiche Plugin-Architektur und seine Fähigkeit, Abhängigkeiten effektiv zu verwalten.

Hauptmerkmale von Maven:

Vorteile von Maven:

Nachteile von Maven:

24.2.2 Gradle

Gradle ist ein modernes Build-Tool, das auf der Groovy- oder Kotlin DSL (Domain Specific Language) basiert. Es kombiniert die Vorteile von deklarativen und imperativen Build-Tools und bietet eine hohe Flexibilität und Performance. Gradle ist besonders für große und komplexe Projekte geeignet und wird oft in modernen Entwicklungsumgebungen eingesetzt.

Hauptmerkmale von Gradle:

Vorteile von Gradle:

Nachteile von Gradle:

24.2.3 Vergleich von Gradle und Maven

Merkmal Maven Gradle
Konfigurationssprache XML Groovy/Kotlin DSL
Flexibilität Geringer, konventionell Hoch, sowohl deklarativ als auch imperativ
Build-Performance Gut Sehr gut durch inkrementelles Bauen
Abhängigkeitsmanagement Zentralisierte Repositories, fest definiert Flexible Repositories, dynamische Versionen
Plugins Umfangreich und etabliert Vielfältig und erweiterbar
Eignung für große Projekte Gut Hervorragend
Lernkurve Niedrig bis mittel Mittel bis hoch

24.2.4 Konfiguration von Maven und Gradle in Spring Boot-Projekten

In diesem Abschnitt zeigen wir, wie man sowohl Maven als auch Gradle in Spring Boot-Projekten konfiguriert, insbesondere für die Contract-First-Entwicklung mit OpenAPI.

24.2.5 Maven-Konfiguration

24.2.5.1 1. Maven-Projekt initialisieren

Maven-Projekte basieren auf einer pom.xml-Datei, die die Projektkonfiguration, Abhängigkeiten und Plugins definiert.

Beispiel einer pom.xml für ein Contract-First-Projekt:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="
           http://maven.apache.org/POM/4.0.0
           http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>buchverwaltung</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>Buchverwaltung</name>
    <description>Eine Spring Boot Anwendung zur Verwaltung von Büchern</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>11</java.version>
        <openapi.generator.version>6.3.0</openapi.generator.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Springdoc OpenAPI UI -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.6.14</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>

        <!-- Weitere Abhängigkeiten -->
    </dependencies>

    <build>
        <plugins>
            <!-- OpenAPI Generator Plugin -->
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>${openapi.generator.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <inputSpec>${project.basedir}/src/main/resources/api/api.yaml</inputSpec>
                            <generatorName>spring</generatorName>
                            <output>${project.build.directory}/generated-sources/openapi</output>
                            <configOptions>
                                <interfaceOnly>true</interfaceOnly>
                                <delegatePattern>true</delegatePattern>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.24</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
        <sourceDirectory>src/main/java</sourceDirectory>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>${project.build.directory}/generated-sources/openapi</directory>
            </resource>
        </resources>
    </build>

    <pluginRepositories>
        <pluginRepository>
            <id>openapitools</id>
            <url>https://plugins.gradle.org/m2/</url>
        </pluginRepository>
    </pluginRepositories>
</project>

Erläuterung der Konfiguration:

24.2.5.2 2. Maven-Projektstruktur

Die typische Maven-Projektstruktur sieht wie folgt aus:

buchverwaltung
│   pom.xml
└───src
    └───main
    │   ├───java
    │   └───resources
    │       └───api
    └───test
        └───java

24.2.5.3 3. Abhängigkeiten und Plugins

24.2.6 Gradle-Konfiguration

24.2.6.1 1. Gradle-Projekt initialisieren

Gradle-Projekte basieren auf einer build.gradle-Datei, die die Projektkonfiguration, Abhängigkeiten und Plugins definiert. Es kann entweder mit Groovy oder Kotlin DSL konfiguriert werden. In diesem Kapitel verwenden wir die Groovy DSL.

Beispiel einer build.gradle für ein Contract-First-Projekt:

plugins {
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.14.RELEASE'
    id 'java'
    id 'org.openapi.generator' version '6.3.0'
}

group = 'com.example'
version = '1.0.0'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springdoc:springdoc-openapi-ui:1.6.14'
    compileOnly 'org.projectlombok:lombok:1.18.24'
    annotationProcessor 'org.projectlombok:lombok:1.18.24'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

openApiGenerate {
    generatorName = 'spring'
    inputSpec = "${projectDir}/src/main/resources/api/api.yaml"
    outputDir = "${buildDir}/generated-sources/openapi"
    configOptions = [
        interfaceOnly   : 'true',
        delegatePattern : 'true'
    ]
}

sourceSets {
    main {
        java {
            srcDirs += "${buildDir}/generated-sources/openapi/src/main/java"
        }
        resources {
            srcDirs += "${buildDir}/generated-sources/openapi/src/main/resources"
        }
    }
}

compileJava.dependsOn openApiGenerate

Erläuterung der Konfiguration:

24.2.6.2 2. Gradle-Projektstruktur

Die typische Gradle-Projektstruktur sieht wie folgt aus:

buchverwaltung
│   build.gradle
│   settings.gradle
└───src
    └───main
    │   ├───java
    │   └───resources
    │       └───api
    └───test
        └───java

24.2.6.3 3. Abhängigkeiten und Plugins

24.2.7 Beispiele für Maven und Gradle Konfigurationen

24.2.7.1 Maven Beispiel

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="
           http://maven.apache.org/POM/4.0.0
           http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>buchverwaltung</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>Buchverwaltung</name>
    <description>Eine Spring Boot Anwendung zur Verwaltung von Büchern</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>11</java.version>
        <openapi.generator.version>6.3.0</openapi.generator.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Springdoc OpenAPI UI -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.6.14</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>

        <!-- Weitere Abhängigkeiten -->
    </dependencies>

    <build>
        <plugins>
            <!-- OpenAPI Generator Plugin -->
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>${openapi.generator.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <inputSpec>${project.basedir}/src/main/resources/api/api.yaml</inputSpec>
                            <generatorName>spring</generatorName>
                            <output>${project.build.directory}/generated-sources/openapi</output>
                            <configOptions>
                                <interfaceOnly>true</interfaceOnly>
                                <delegatePattern>true</delegatePattern>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.24</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
        <sourceDirectory>src/main/java</sourceDirectory>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>${project.build.directory}/generated-sources/openapi</directory>
            </resource>
        </resources>
    </build>

    <pluginRepositories>
        <pluginRepository>
            <id>openapitools</id>
            <url>https://plugins.gradle.org/m2/</url>
        </pluginRepository>
    </pluginRepositories>
</project>

24.2.7.2 Gradle Beispiel

plugins {
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.14.RELEASE'
    id 'java'
    id 'org.openapi.generator' version '6.3.0'
}

group = 'com.example'
version = '1.0.0'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springdoc:springdoc-openapi-ui:1.6.14'
    compileOnly 'org.projectlombok:lombok:1.18.24'
    annotationProcessor 'org.projectlombok:lombok:1.18.24'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

openApiGenerate {
    generatorName = 'spring'
    inputSpec = "${projectDir}/src/main/resources/api/api.yaml"
    outputDir = "${buildDir}/generated-sources/openapi"
    configOptions = [
        interfaceOnly   : 'true',
        delegatePattern : 'true'
    ]
}

sourceSets {
    main {
        java {
            srcDirs += "${buildDir}/generated-sources/openapi/src/main/java"
        }
        resources {
            srcDirs += "${buildDir}/generated-sources/openapi/src/main/resources"
        }
    }
}

compileJava.dependsOn openApiGenerate

24.2.7.3 3. Code-Generierung und Integration

Sowohl Maven als auch Gradle bieten Plugins, die den OpenAPI Generator integrieren und automatisierte Code-Generierung ermöglichen. Dies stellt sicher, dass der generierte Code stets aktuell und konsistent mit der API-Spezifikation ist.

Schritte zur Code-Generierung:

  1. API-Spezifikation erstellen oder aktualisieren:
  2. Build-Tool konfigurieren:
  3. Code generieren:
  4. Generierten Code integrieren:
  5. Geschäftslogik implementieren:

24.3 Beispiele für die Nutzung von Maven und Gradle in Spring Boot

24.3.1 Maven Beispiel: Implementierung des BookController

Nach der Code-Generierung wird der BookController generiert. Implementieren Sie die Methoden wie folgt:

package com.example.generated.api;

import com.example.generated.model.Book;
import com.example.generated.repository.BookRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;

import java.util.List;
import java.util.Optional;

@Controller
public class BookControllerImpl implements BookController {

    private final BookRepository bookRepository;

    public BookControllerImpl(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Override
    public ResponseEntity<List<Book>> getBooks() {
        List<Book> books = bookRepository.findAll();
        return ResponseEntity.ok(books);
    }

    @Override
    public ResponseEntity<Book> getBookById(Long id) {
        Optional<Book> book = bookRepository.findById(id);
        return book.map(ResponseEntity::ok)
                   .orElse(ResponseEntity.notFound().build());
    }

    @Override
    public ResponseEntity<Book> createBook(Book book) {
        Book savedBook = bookRepository.save(book);
        return ResponseEntity.status(201).body(savedBook);
    }

    // Weitere Methoden (updateBook, deleteBook etc.)
}

24.3.2 Gradle Beispiel: Implementierung des BookController

Nach der Code-Generierung wird der BookController generiert. Implementieren Sie die Methoden wie folgt:

package com.example.generated.api;

import com.example.generated.model.Book;
import com.example.generated.repository.BookRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;

import java.util.List;
import java.util.Optional;

@Controller
public class BookControllerImpl implements BookController {

    private final BookRepository bookRepository;

    public BookControllerImpl(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Override
    public ResponseEntity<List<Book>> getBooks() {
        List<Book> books = bookRepository.findAll();
        return ResponseEntity.ok(books);
    }

    @Override
    public ResponseEntity<Book> getBookById(Long id) {
        Optional<Book> book = bookRepository.findById(id);
        return book.map(ResponseEntity::ok)
                   .orElse(ResponseEntity.notFound().build());
    }

    @Override
    public ResponseEntity<Book> createBook(Book book) {
        Book savedBook = bookRepository.save(book);
        return ResponseEntity.status(201).body(savedBook);
    }

    // Weitere Methoden (updateBook, deleteBook etc.)
}

24.3.3 Datenmodell und Repository

24.3.3.1 Book.java

package com.example.generated.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
    private Long id;
    private String title;
    private String author;
}

24.3.3.2 BookRepository.java

package com.example.generated.repository;

import com.example.generated.model.Book;
import org.springframework.stereotype.Repository;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Repository
public class BookRepository {
    private final Map<Long, Book> bookStore = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong();

    public Book save(Book book) {
        if (book.getId() == null) {
            book.setId(idGenerator.incrementAndGet());
        }
        bookStore.put(book.getId(), book);
        return book;
    }

    public Optional<Book> findById(Long id) {
        return Optional.ofNullable(bookStore.get(id));
    }

    public List<Book> findAll() {
        return new ArrayList<>(bookStore.values());
    }

    public boolean deleteById(Long id) {
        return bookStore.remove(id) != null;
    }
}

24.3.4 Anwendung ausführen und testen

Testen der API mit cURL:

Antwort:

{
  "id": 1,
  "title": "Spring Boot in Action",
  "author": "Craig Walls"
}

24.4 Best Practices für Gradle und Maven in Spring Boot

  1. Verwenden Sie zentrale Abhängigkeitsmanagement-Tools:
  2. Automatisierte Code-Generierung integrieren:
  3. Versionskontrolle der Build-Skripte:
  4. Nutzen Sie Profiles für unterschiedliche Umgebungen:
  5. Optimieren Sie die Build-Performance:
  6. Implementieren Sie eine klare Versionierungsstrategie:
  7. Integrieren Sie umfassende Tests:
  8. Automatisiertes Deployment einrichten:
  9. Nutzen Sie Plugins zur Erweiterung der Funktionalitäten:
  10. Dokumentation und Schulung:

24.5 Fallstudie: Nutzung von Gradle und Maven in einem Microservices-Projekt

Projekt: Entwicklung einer E-Commerce-Plattform mit Microservices für Benutzerverwaltung, Produktkatalog und Bestellabwicklung.

  1. Erstellung der API-Spezifikationen:
  2. Konfiguration der Build-Tools:
  3. Automatisierte Code-Generierung:
  4. Implementierung der Geschäftslogik:
  5. Automatisierte Tests und Validierung:
  6. Deployment und Monitoring:

Vorteile:

24.6 Best Practices für Gradle und Maven in Spring Boot

  1. Zentrale Abhängigkeitsverwaltung nutzen:
  2. Automatisierte Code-Generierung integrieren:
  3. Versionskontrolle der Build-Skripte:
  4. Profiles für unterschiedliche Umgebungen nutzen:
  5. Optimierung der Build-Performance:
  6. Klare Versionierungsstrategie implementieren:
  7. Umfassende Tests implementieren:
  8. Automatisiertes Deployment einrichten:
  9. Verwendung von Plugins zur Erweiterung der Funktionalitäten:
  10. Dokumentation und Schulung sicherstellen:

24.7 tl;dr

Gradle und Maven sind die führenden Build-Tools in der Java- und Spring Boot-Entwicklung. Während Maven durch seine standardisierte XML-basierte Konfiguration und Reife besticht, bietet Gradle durch seine flexible DSL und bessere Performance moderne Features für komplexe Projekte. Beide Tools ermöglichen die Integration von OpenAPI Generator, um automatisierte Server-Stubs und Modelle aus API-Spezifikationen zu generieren, was besonders in der Contract-First-Entwicklung von Vorteil ist. Best Practices umfassen zentrale Abhängigkeitsverwaltung, automatisierte Code-Generierung, klare Versionierungsstrategien, umfassende Tests und die Integration in CI/CD-Pipelines. Die Wahl zwischen Gradle und Maven hängt von den spezifischen Projektanforderungen und Teampräferenzen ab, beide bieten jedoch umfangreiche Möglichkeiten zur effizienten und konsistenten Erstellung und Verwaltung von Spring Boot-Anwendungen.

Durch die Anwendung dieser Methoden und die Nutzung der Stärken beider Build-Tools können Entwickler robuste, skalierbare und gut dokumentierte Spring Boot-Anwendungen erstellen, die den Anforderungen moderner Softwareprojekte gerecht werden.