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.
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.
| Dimension | Legacy | Modern | Migrationskonsequenz |
|---|---|---|---|
| Programmiermodell | imperativ / ohne Framework-Annotationen | Bean, Configuration, SpringBootApplication | Annotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen. |
| Abhängigkeiten | 1 direkte Dependencies | 5 direkte Dependencies | 5 neu, 1 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen. |
| Öffentliche API | 3 erkannte Methoden | 1 erkannte Methoden | Methoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag. |
| Datenmodell | class BatchLegacyApplication, class CsvOrderParser, class LegacyBatchImporter, record OrderRow | class BatchConfiguration, class BatchModernApplication, record OrderRow | Feldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet. |
| Fehlerverhalten | Legacy-Exceptions und Rückgabewerte | explizitere Fach-/Framework-Fehlerabbildung | Fehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden. |
| Tests | Bestands- und Golden-Master-Tests | Unit-, Slice-, Contract- und Integrationstests | Der Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt. |
| Betrieb | Legacy-Start/Lifecycle | modernes Packaging, Health und externe Konfiguration | Parallelbetrieb, Replay/Retry und Rückrouting müssen vor Abschaltung des Legacy-Pfads erprobt sein. |
| Rollback | Legacy-Artefakt bleibt unverändert | Modern-Artefakt getrennt deploybar | Kein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein. |
restartability, duplicate processing and bad-record policy
job launch, restart, skip/retry and idempotency tests
Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.
| Legacy-Rolle / Datei | Modern-Rolle / Datei | Bedeutung |
|---|---|---|
| LegacyBatchImporter.java + CsvOrderParser.java | BatchConfiguration.java | Imperativer Ablauf wird Reader/Processor/Writer-Pipeline. |
| OrderRow.java | OrderRow.java | Fachlicher Datensatz bleibt stabil. |
| BatchLegacyApplication.java | BatchModernApplication.java | Manueller Start wird Spring-Batch-Jobstart. |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| BatchLegacyApplication.java | class BatchLegacyApplication | — | void main(String[] args) | — | 13 |
| CsvOrderParser.java | class CsvOrderParser | — | OrderRow parse(String line) | Mapper | 13 |
| LegacyBatchImporter.java | class LegacyBatchImporter | — | List<OrderRow> importFile(Path path) | Template Method (manuell angedeutet) | 17 |
| OrderRow.java | record OrderRow | — | — | — | 6 |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| BatchConfiguration.java | class BatchConfiguration | Bean, Configuration | — | Pipeline | 78 |
| BatchModernApplication.java | class BatchModernApplication | SpringBootApplication | void main(String[] args) | — | 11 |
| OrderRow.java | record OrderRow | — | — | — | 6 |
| Status | Dependency | Version | Scope | Prüfung |
|---|---|---|---|---|
| ENTFERNT | org.junit.jupiter:junit-jupiter | BOM/Parent | test | Legacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt. |
| NEU | com.h2database:h2 | BOM/Parent | runtime | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
| NEU | org.springframework.batch:spring-batch-test | BOM/Parent | test | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
| NEU | org.springframework.boot:spring-boot-starter-batch | BOM/Parent | compile | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
| NEU | org.springframework.boot:spring-boot-starter-jdbc | BOM/Parent | compile | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
| NEU | org.springframework.boot:spring-boot-starter-test | BOM/Parent | test | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.
Migrationsbedeutung: Imperativer Ablauf wird Reader/Processor/Writer-Pipeline.
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();
}
}
}
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()));
}
}
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();
}
}
Migrationsbedeutung: Fachlicher Datensatz bleibt stabil.
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) {
}
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) {
}
--- OrderRow.java
+++ OrderRow.java
@@ -1,4 +1,4 @@
-package at.aydin.lab.batch.legacy;
+package at.aydin.lab.batch.modern;
import java.math.BigDecimal;
Migrationsbedeutung: Manueller Start wird Spring-Batch-Jobstart.
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);
}
}
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);
}
}
--- 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);
}
}
| ID | Ebene | Prüfung | erforderlicher Nachweis |
|---|---|---|---|
| 06-BATCH-A01 | Integration/Contract | Erstlauf und Restart ergeben denselben Endbestand. | Automatisierter Test und CI-Protokoll |
| 06-BATCH-A02 | Integration/Contract | Fehlerzeilen sind vollständig nachvollziehbar. | Automatisierter Test und CI-Protokoll |
| 06-BATCH-A03 | Integration/Contract | Keine doppelte Verarbeitung nach Prozessabbruch. | Automatisierter Test und CI-Protokoll |
| 06-BATCH-A04 | Integration/Contract | Chunk-Transaktionen sind konsistent. | Automatisierter Test und CI-Protokoll |
| 06-BATCH-A05 | Integration/Contract | Jobparameter verhindern versehentliche Doppelstarts. | Automatisierter Test und CI-Protokoll |
| 06-BATCH-T01 | Unit | Fachlogik ohne Container oder externen Dienst testen. | Unit-Test |
| 06-BATCH-T02 | Regression | Legacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen. | Vergleichsreport |
| 06-BATCH-T03 | Negative | Fehlerhafte, leere und grenzwertige Eingaben prüfen. | Negativtest |
| 06-BATCH-T04 | Operations | Start, Health, Shutdown und Konfigurationsfehler prüfen. | Deployment-/Startprotokoll |
| 06-BATCH-T05 | Contract | Protokoll, Status/Headers, Payload und Timeout gegen Consumer Contract prüfen. | Contract-Test |
| 06-BATCH-F01 | Fokus | job launch, restart, skip/retry and idempotency tests | Modulspezifischer Testreport |
| Risiko | Auswirkung | Gegenmaßnahme | Gate |
|---|---|---|---|
| restartability, duplicate processing and bad-record policy | MITTEL | job launch, restart, skip/retry and idempotency tests | vor Cutover |
| Neue Framework-/Library-Laufzeitabhängigkeiten | MITTEL | SBOM, Lizenz-, CVE- und transitive Dependency-Prüfung im Online-Build | vor Release |
| Legacy-Verhalten war implizit an entfernte Library gekoppelt | MITTEL | Contract-/Golden-Master-Tests und gezielte Fehlerpfade | vor Abschaltung |
| Seite | Ressource | Zeilen |
|---|---|---|
| Legacy | legacy/src/main/resources/sample-orders.csv | 4 |
| Modern | modern/src/main/resources/application.properties | 3 |
| Modern | modern/src/main/resources/sample-orders.csv | 4 |
| Modern | modern/src/main/resources/schema.sql | 1 |
Der alte Importer bleibt für einen begrenzten Zeitraum read-only verfügbar; ein Laufprotokoll verhindert, dass dieselbe Datei von beiden Varianten verarbeitet wird.