MFormations
Modern Java Engineering

Chapitre 13

13 - DevOps Java

13 - DevOps Java

Cours 13 : DevOps Java

1. Maven Avancé

1.1 Maven Lifecycle

validate
  ↓
initialize
  ↓
generate-sources
  ↓
process-sources
  ↓
compile        ← Compile le code source
  ↓
process-classes
  ↓
generate-test-sources
  ↓
process-test-sources
  ↓
test-compile
  ↓
test           ← Exécute les tests
  ↓
prepare-package
  ↓
package        ← Crée le JAR/WAR
  ↓
verify
  ↓
install        ← Installe dans le repository local
  ↓
deploy         ← Déploie sur le repository distant

1.2 Plugins essentiels

<build>
    <plugins>
        <!-- Compilation avec vérification -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.12.1</version>
            <configuration>
                <release>21</release>
                <parameters>true</parameters>
            </configuration>
        </plugin>
        
        <!-- Tests avec JUnit 5 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.3</version>
        </plugin>
        
        <!-- Analyse de couverture -->
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.11</version>
            <executions>
                <execution>
                    <goals><goal>prepare-agent</goal></goals>
                </execution>
                <execution>
                    <id>report</id>
                    <phase>verify</phase>
                    <goals><goal>report</goal></goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

1.3 Multi-module Maven

<!-- Parent POM -->
<groupId>com.orderhub</groupId>
<artifactId>orderhub-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<modules>
    <module>order-domain</module>
    <module>order-application</module>
    <module>order-adapter-rest</module>
    <module>order-adapter-persistence</module>
    <module>order-boot</module>
</modules>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

1.4 BOM (Bill of Materials)

<!-- orderhub-bom/pom.xml -->
<project>
    <groupId>com.orderhub</groupId>
    <artifactId>orderhub-bom</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    
    <properties>
        <spring-boot.version>3.2.0</spring-boot.version>
        <spring-cloud.version>2023.0.0</spring-cloud.version>
        <axon.version>4.9.0</axon.version>
        <testcontainers.version>1.19.3</testcontainers.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.axonframework</groupId>
                <artifactId>axon-bom</artifactId>
                <version>${axon.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

2. Gradle

2.1 Kotlin DSL

plugins {
    java
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
    jacoco
}

group = "com.orderhub"
version = "1.0.0"

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
    }
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("org.postgresql:postgresql")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.testcontainers:postgresql")
}

tasks.test {
    useJUnitPlatform()
    finalizedBy(tasks.jacocoTestReport)
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    reports {
        xml.required.set(true)
        html.required.set(true)
    }
}

2.2 Gradle Caching & Optimisation

// settings.gradle.kts
pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}

// Cache des dépendances
val gradleCacheDir = file("${System.getProperty("user.home")}/.gradle/caches")

// Build cache
buildCache {
    local {
        isEnabled = true
        directory = file("${rootDir}/.build-cache")
    }
    remote<HttpBuildCache> {
        url = uri("https://build-cache.orderhub.com/cache/")
        isPush = true
    }
}

3. Docker

3.1 Multi-stage Build

# Stage 1 : Build avec Maven
FROM maven:3.9-eclipse-temurin-21-alpine AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Stage 2 : Image finale légère
FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080

# JVM flags optimisés
ENV JAVA_OPTS="-XX:+UseZGC \
    -XX:MaxRAMPercentage=75.0 \
    -XX:+ExitOnOutOfMemoryError \
    -Xlog:gc*:file=/dev/gc.log:time,uptime:filecount=5,filesize=10m"

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

3.2 Docker Compose pour les microservices

version: '3.8'
services:
  order-service:
    build: ./order-service
    ports:
      - "8081:8080"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      DB_URL: jdbc:postgresql://postgres:5432/orders
      KAFKA_BOOTSTRAP: kafka:9092
    depends_on:
      - postgres
      - kafka
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: orders
      POSTGRES_USER: orderuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U orderuser"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

4. CI/CD avec GitHub Actions

4.1 Pipeline Maven

name: Build & Test
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v4
      - name: Setup Java 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: 'maven'
      - name: Build & Test
        run: mvn verify -B
      - name: SonarQube Analysis
        run: mvn sonar:sonar
          -Dsonar.host.url=${{ secrets.SONAR_HOST_URL }}
          -Dsonar.login=${{ secrets.SONAR_TOKEN }}
      - name: Build Docker Image
        run: docker build -t order-service:${{ github.sha }} .

4.2 Pipeline Gradle avec cache

name: Gradle CI
on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2
        with:
          cache-read-only: ${{ github.ref != 'refs/heads/main' }}
      - name: Build with Gradle
        run: ./gradlew build
      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: build/libs/*.jar

4.3 Pipeline de release

name: Release
on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
      - name: Build
        run: mvn versions:set -DnewVersion=${{ env.VERSION }} && mvn clean package
      - name: Publish to Nexus
        run: mvn deploy
        env:
          NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
          NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
      - name: Generate SBOM
        run: mvn org.cyclonedx:cyclonedx-maven-plugin:makeBom
      - name: Sign Package
        run: gpg --sign --detach-sig target/*.jar

5. SonarQube

5.1 Configuration

<plugin>
    <groupId>org.sonarsource.scanner.maven</groupId>
    <artifactId>sonar-maven-plugin</artifactId>
    <version>3.10.0.2594</version>
</plugin>

5.2 Qualité Gates

# sonar-project.properties
sonar.projectKey=com.orderhub:order-service
sonar.projectName=Order Service
sonar.qualitygate.wait=true
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
sonar.java.coveragePlugin=jacoco
sonar.junit.reportPaths=target/surefire-reports
sonar.issues.html.ignoreStatuses=RESOLVED,CLOSED

6. Nexus/Artifactory

6.1 Configuration Nexus

<distributionManagement>
    <repository>
        <id>nexus-releases</id>
        <url>https://nexus.orderhub.com/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
        <id>nexus-snapshots</id>
        <url>https://nexus.orderhub.com/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

<repositories>
    <repository>
        <id>nexus</id>
        <url>https://nexus.orderhub.com/repository/maven-public/</url>
    </repository>
</repositories>

7. SBOM (Software Bill of Materials)

7.1 Génération avec CycloneDX

<plugin>
    <groupId>org.cyclonedx</groupId>
    <artifactId>cyclonedx-maven-plugin</artifactId>
    <version>2.7.9</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>makeBom</goal></goals>
        </execution>
    </executions>
    <configuration>
        <projectType>application</projectType>
        <schemaVersion>1.5</schemaVersion>
        <includeBomSerialNumber>true</includeBomSerialNumber>
        <includeCompileScope>true</includeCompileScope>
        <includeProvidedScope>true</includeProvidedScope>
        <includeRuntimeScope>true</includeRuntimeScope>
        <includeSystemScope>false</includeSystemScope>
        <includeTestScope>false</includeTestScope>
        <outputFormat>json</outputFormat>
        <outputName>bom</outputName>
    </configuration>
</plugin>

8. Package Signing

8.1 GPG Signature

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>3.1.0</version>
    <executions>
        <execution>
            <id>sign-artifacts</id>
            <phase>verify</phase>
            <goals><goal>sign</goal></goals>
        </execution>
    </executions>
</plugin>

Points clés

  • Maven lifecycle : validate → compile → test → package → verify → install → deploy
  • Gradle Kotlin DSL pour des builds type-safe
  • Docker multi-stage pour des images légères (JRE seulement)
  • CI/CD avec cache Maven/Gradle pour des builds rapides
  • SonarQube pour la qualité et la sécurité du code
  • Nexus/Artifactory pour la gestion des artefacts
  • SBOM CycloneDX pour la traçabilité des dépendances
  • GPG signing pour l'intégrité des packages