Modul 06 · Migration Deep Dive · Stufe 5 Enterprise Runtime

Batch

manueller CSV-Ablauf wird nachvollziehbar in Spring Batch Pipeline überführt. Die Akte zeigt nicht nur das Ziel, sondern die tatsächlichen Dateien, APIs, Dependencies, Codebelege, Tests, Risiken und Cutover-Schritte.

MapperTemplate MethodPipeline
4 → 3Produktionsdateien
49 → 95Java-Zeilen
1 → 1Testdateien
1 / 5Dependencies entfernt / neu
MITTELRisiko · Score 4

Was sich konkret ändert

DimensionLegacyModernMigrationskonsequenz
Programmiermodellimperativ / ohne Framework-AnnotationenBean, Configuration, SpringBootApplicationAnnotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen.
Abhängigkeiten1 direkte Dependencies5 direkte Dependencies5 neu, 1 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen.
Öffentliche API3 erkannte Methoden1 erkannte MethodenMethoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag.
Datenmodellclass BatchLegacyApplication, class CsvOrderParser, class LegacyBatchImporter, record OrderRowclass BatchConfiguration, class BatchModernApplication, record OrderRowFeldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet.
FehlerverhaltenLegacy-Exceptions und Rückgabewerteexplizitere Fach-/Framework-FehlerabbildungFehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden.
TestsBestands- und Golden-Master-TestsUnit-, Slice-, Contract- und IntegrationstestsDer Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt.
BetriebLegacy-Start/Lifecyclemodernes Packaging, Health und externe KonfigurationParallelbetrieb, Replay/Retry und Rückrouting müssen vor Abschaltung des Legacy-Pfads erprobt sein.
RollbackLegacy-Artefakt bleibt unverändertModern-Artefakt getrennt deploybarKein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein.

Fachlicher Vertrag: unverändert zu erhalten

  • Jede Eingabezeile wird höchstens einmal fachlich wirksam.
  • Restart setzt an einem konsistenten Checkpoint fort.
  • Fehlerhafte Datensätze werden nach klarer Skip-/Fail-Regel behandelt.

Hauptrisiko und Testfokus

restartability, duplicate processing and bad-record policy

job launch, restart, skip/retry and idempotency tests

Die Modernisierung gilt erst als abgeschlossen, wenn der fachliche Vertrag automatisiert belegt ist.

Verbindliches Code- und Rollenmapping

Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.

Legacy-Rolle / DateiModern-Rolle / DateiBedeutung
LegacyBatchImporter.java + CsvOrderParser.javaBatchConfiguration.javaImperativer Ablauf wird Reader/Processor/Writer-Pipeline.
OrderRow.javaOrderRow.javaFachlicher Datensatz bleibt stabil.
BatchLegacyApplication.javaBatchModernApplication.javaManueller Start wird Spring-Batch-Jobstart.

Dependency- und Laufzeitmigration

StatusDependencyVersionScopePrüfung
ENTFERNTorg.junit.jupiter:junit-jupiterBOM/ParenttestLegacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt.
NEUcom.h2database:h2BOM/ParentruntimeNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.batch:spring-batch-testBOM/ParenttestNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.boot:spring-boot-starter-batchBOM/ParentcompileNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.boot:spring-boot-starter-jdbcBOM/ParentcompileNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.boot:spring-boot-starter-testBOM/ParenttestNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
Direkte POM-Daten ersetzen keinen erfolgreichen Online-Build mit transitiver SBOM- und CVE-Prüfung.

Vorher-/Nachher-Codebelege

Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.

Legacy
LegacyBatchImporter.java + CsvOrderParser.java
Modern
BatchConfiguration.java

Migrationsbedeutung: Imperativer Ablauf wird Reader/Processor/Writer-Pipeline.

Legacy-Code

projects/06-batch/legacy/src/main/java/at/aydin/lab/batch/legacy/LegacyBatchImporter.java

package at.aydin.lab.batch.legacy;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

// Design Pattern: Template Method (manuell angedeutet)
// Zweck: Lesen, Überspringen, Parsen und Schreiben bilden einen festen Batch-Ablauf.
public final class LegacyBatchImporter {
    private final CsvOrderParser parser = new CsvOrderParser();
    public List<OrderRow> importFile(Path path) throws IOException {
        try (var lines = Files.lines(path)) {
            return lines.skip(1).filter(line -> !line.isBlank()).map(parser::parse).toList();
        }
    }
}

Legacy-Code

projects/06-batch/legacy/src/main/java/at/aydin/lab/batch/legacy/CsvOrderParser.java

package at.aydin.lab.batch.legacy;

import java.math.BigDecimal;

// Design Pattern: Mapper
// Zweck: Transportformat CSV wird in ein fachliches Objekt übersetzt.
public final class CsvOrderParser {
    public OrderRow parse(String line) {
        String[] columns = line.split(",", -1);
        if (columns.length != 3) throw new IllegalArgumentException("Ungültige CSV-Zeile: " + line);
        return new OrderRow(Long.parseLong(columns[0].trim()), columns[1].trim(), new BigDecimal(columns[2].trim()));
    }
}

Modern-Code

projects/06-batch/modern/src/main/java/at/aydin/lab/batch/modern/BatchConfiguration.java

package at.aydin.lab.batch.modern;

import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
// Design Pattern: Pipeline
// Zweck: Reader, Processor und Writer sind austauschbare, getrennt testbare Batch-Stufen.
public class BatchConfiguration {
    @Bean
    FlatFileItemReader<OrderRow> orderReader() {
        return new FlatFileItemReaderBuilder<OrderRow>()
                .name("orderReader")
                .resource(new ClassPathResource("sample-orders.csv"))
                .linesToSkip(1)
                .delimited()
                .names("id", "customer", "amount")
                .fieldSetMapper(fieldSet -> new OrderRow(
                        fieldSet.readLong("id"),
                        fieldSet.readString("customer"),
                        fieldSet.readBigDecimal("amount")))
                .build();
    }

    @Bean
    ItemProcessor<OrderRow, OrderRow> positiveAmountProcessor() {
        return item -> item.amount().signum() > 0 ? item : null;
    }

    @Bean
    JdbcBatchItemWriter<OrderRow> orderWriter(DataSource dataSource) {
        return new JdbcBatchItemWriterBuilder<OrderRow>()
                .dataSource(dataSource)
                .sql("insert into imported_order(id,customer,amount) "
                        + "values (:id,:customer,:amount)")
                .itemSqlParameterSourceProvider(item -> new MapSqlParameterSource()
                        .addValue("id", item.id())
                        .addValue("customer", item.customer())
                        .addValue("amount", item.amount()))
                .build();
    }

    @Bean
    Step importOrders(
            JobRepository jobRepository,
            PlatformTransactionManager transactionManager,
            FlatFileItemReader<OrderRow> orderReader,
            ItemProcessor<OrderRow, OrderRow> positiveAmountProcessor,
            JdbcBatchItemWriter<OrderRow> orderWriter) {
        return new StepBuilder("importOrders", jobRepository)
                .<OrderRow, OrderRow>chunk(10, transactionManager)
                .reader(orderReader)
                .processor(positiveAmountProcessor)
                .writer(orderWriter)
                .build();
    }

    @Bean
    Job importOrderJob(JobRepository jobRepository, Step importOrders) {
        return new JobBuilder("importOrderJob", jobRepository)
                .start(importOrders)
                .build();
    }
}
Legacy
OrderRow.java
Modern
OrderRow.java

Migrationsbedeutung: Fachlicher Datensatz bleibt stabil.

Legacy-Code

projects/06-batch/legacy/src/main/java/at/aydin/lab/batch/legacy/OrderRow.java

package at.aydin.lab.batch.legacy;

import java.math.BigDecimal;

public record OrderRow(long id, String customer, BigDecimal amount) {
}

Modern-Code

projects/06-batch/modern/src/main/java/at/aydin/lab/batch/modern/OrderRow.java

package at.aydin.lab.batch.modern;

import java.math.BigDecimal;

public record OrderRow(long id, String customer, BigDecimal amount) {
}
Technischen Unified Diff öffnen
--- OrderRow.java
+++ OrderRow.java
@@ -1,4 +1,4 @@
-package at.aydin.lab.batch.legacy;
+package at.aydin.lab.batch.modern;
 
 import java.math.BigDecimal;
 
Legacy
BatchLegacyApplication.java
Modern
BatchModernApplication.java

Migrationsbedeutung: Manueller Start wird Spring-Batch-Jobstart.

Legacy-Code

projects/06-batch/legacy/src/main/java/at/aydin/lab/batch/legacy/BatchLegacyApplication.java

package at.aydin.lab.batch.legacy;

import java.nio.file.Path;

public final class BatchLegacyApplication {
    private BatchLegacyApplication() {
    }

    public static void main(String[] args) throws Exception {
        Path input = args.length == 0 ? Path.of("src/main/resources/sample-orders.csv") : Path.of(args[0]);
        new LegacyBatchImporter().importFile(input).forEach(System.out::println);
    }
}

Modern-Code

projects/06-batch/modern/src/main/java/at/aydin/lab/batch/modern/BatchModernApplication.java

package at.aydin.lab.batch.modern;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BatchModernApplication {
    public static void main(String[] args) {
        SpringApplication.run(BatchModernApplication.class, args);
    }
}
Technischen Unified Diff öffnen
--- BatchLegacyApplication.java
+++ BatchModernApplication.java
@@ -1,13 +1,11 @@
-package at.aydin.lab.batch.legacy;
+package at.aydin.lab.batch.modern;
 
-import java.nio.file.Path;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
 
-public final class BatchLegacyApplication {
-    private BatchLegacyApplication() {
-    }
-
-    public static void main(String[] args) throws Exception {
-        Path input = args.length == 0 ? Path.of("src/main/resources/sample-orders.csv") : Path.of(args[0]);
-        new LegacyBatchImporter().importFile(input).forEach(System.out::println);
+@SpringBootApplication
+public class BatchModernApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(BatchModernApplication.class, args);
     }
 }

Umsetzungsplan mit Qualitäts-Gates

  1. Arbeitspaket 1
    CSV-Format, Header, Zeichensatz und Dezimalregeln als Input Contract festhalten. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  2. Arbeitspaket 2
    CsvOrderParser-Logik in Reader/Processor/Writer-Verantwortungen zerlegen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  3. Arbeitspaket 3
    JobRepository und persistente Metadaten konfigurieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  4. Arbeitspaket 4
    Idempotenzschlüssel und Duplicate-Policy definieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  5. Arbeitspaket 5
    Skip-, Retry-, Restart- und Abbruchgrenzen mit realistischen Dateien testen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  6. Arbeitspaket 6
    Scheduler/Operator erst nach identischen Summen- und Fehlerreports umstellen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.

Konkreter Test- und Abnahmekatalog

IDEbenePrüfungerforderlicher Nachweis
06-BATCH-A01Integration/ContractErstlauf und Restart ergeben denselben Endbestand.Automatisierter Test und CI-Protokoll
06-BATCH-A02Integration/ContractFehlerzeilen sind vollständig nachvollziehbar.Automatisierter Test und CI-Protokoll
06-BATCH-A03Integration/ContractKeine doppelte Verarbeitung nach Prozessabbruch.Automatisierter Test und CI-Protokoll
06-BATCH-A04Integration/ContractChunk-Transaktionen sind konsistent.Automatisierter Test und CI-Protokoll
06-BATCH-A05Integration/ContractJobparameter verhindern versehentliche Doppelstarts.Automatisierter Test und CI-Protokoll
06-BATCH-T01UnitFachlogik ohne Container oder externen Dienst testen.Unit-Test
06-BATCH-T02RegressionLegacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen.Vergleichsreport
06-BATCH-T03NegativeFehlerhafte, leere und grenzwertige Eingaben prüfen.Negativtest
06-BATCH-T04OperationsStart, Health, Shutdown und Konfigurationsfehler prüfen.Deployment-/Startprotokoll
06-BATCH-T05ContractProtokoll, Status/Headers, Payload und Timeout gegen Consumer Contract prüfen.Contract-Test
06-BATCH-F01Fokusjob launch, restart, skip/retry and idempotency testsModulspezifischer Testreport

Risikoregister des Moduls

RisikoAuswirkungGegenmaßnahmeGate
restartability, duplicate processing and bad-record policyMITTELjob launch, restart, skip/retry and idempotency testsvor Cutover
Neue Framework-/Library-LaufzeitabhängigkeitenMITTELSBOM, Lizenz-, CVE- und transitive Dependency-Prüfung im Online-Buildvor Release
Legacy-Verhalten war implizit an entfernte Library gekoppeltMITTELContract-/Golden-Master-Tests und gezielte Fehlerpfadevor Abschaltung

Rollback und Koexistenz

Der alte Importer bleibt für einen begrenzten Zeitraum read-only verfügbar; ein Laufprotokoll verhindert, dass dieselbe Datei von beiden Varianten verarbeitet wird.

Abbruchkriterien

  • Fachlicher Golden-Master weicht ab.
  • Daten-, Nachrichten- oder API-Kompatibilität ist ungeklärt.
  • Fehlerquote, Latenz oder Ressourcenverbrauch überschreiten das vereinbarte Limit.
  • Monitoring oder Rückfallpfad ist nicht funktionsfähig.

Definition of Done

Direkte Arbeitslinks

⌂ Cockpit