Batch / Scheduler / Nachtverarbeitung · Prio 9 · Vorher/Nachher

Batch: restartfähige Verarbeitung schneiden

Ein Shell/JCL-ähnlicher Nachtjob verarbeitet Dateien, Datenbankupdates und Reports in einem Lauf. Nach dem Refactoring gibt es Steps, Kontrolltabelle, Checkpoints und klare Wiederanlaufpunkte.

← Refactoring Übersicht
Batch: restartfähige Verarbeitung schneiden Diagramm
Fachlich-technischer Schnitt vom Legacy-Problem zur refactorbaren Zielstruktur.
VorherSkript macht alles
Schritt 1Step-Protokoll
Schritt 2idempotente Writer
Nachherrestartbare Pipeline
Problemverständnis
AspektBeschreibung
SymptomBei Fehler muss der gesamte Nachtlauf manuell neu gestartet oder per SQL repariert werden.
RisikoDoppelte Dateiimporte, falsche Reports oder verpasster Tagesabschluss.
Refactoring-ZielJob in stabile Steps schneiden, jeden Step idempotent machen und Laufstatus dokumentieren.
PipelineCheckpointTemplate MethodRepositoryIdempotent WriterObserver/Monitoring
Vorher: monolithisches Batch-Skript
Warum kritisch: Dieser Code mischt Verantwortlichkeiten. Er ist nicht automatisch schlecht, aber Änderungen sind teuer, weil Fachlogik, Infrastruktur und Fehlerbehandlung verklebt sind.
Vorher: monolithisches Batch-Skript
#!/bin/bash
set -e
RUN_DATE=$1
FILE=/transfer/in/orders_${RUN_DATE}.csv
LOG=/logs/nightly_${RUN_DATE}.log

echo "start nightly" >> $LOG
sqlplus billing/billing@ORCL <<SQL
TRUNCATE TABLE STG_ORDER;
SQL

while IFS=';' read -r customer amount article; do
  sqlplus billing/billing@ORCL <<SQL
INSERT INTO STG_ORDER(customer_no, amount, article_no, run_date)
VALUES('$customer', $amount, '$article', TO_DATE('$RUN_DATE','YYYYMMDD'));
SQL
done < $FILE

sqlplus billing/billing@ORCL @/jobs/apply_orders.sql $RUN_DATE
sqlplus billing/billing@ORCL @/jobs/create_reports.sql $RUN_DATE
mv $FILE /transfer/archive/
echo "done" >> $LOG
Kontrolltabelle für Wiederanlauf
Refactoring-Regel: Erst Verhalten sichern, dann Struktur ändern. Ohne Golden Master oder Characterization Test kann ein schönes Design fachlich falsch sein.
Kontrolltabelle für Wiederanlauf
CREATE TABLE BATCH_STEP_RUN (
  RUN_ID        VARCHAR2(40) NOT NULL,
  STEP_NAME     VARCHAR2(80) NOT NULL,
  STATUS        VARCHAR2(20) NOT NULL,
  STARTED_AT    TIMESTAMP,
  FINISHED_AT   TIMESTAMP,
  ROWS_READ     NUMBER,
  ROWS_WRITTEN  NUMBER,
  ERROR_TEXT    VARCHAR2(1000),
  CONSTRAINT PK_BATCH_STEP_RUN PRIMARY KEY (RUN_ID, STEP_NAME)
);
Nachher: restartbare Step-Pipeline mit Kontrolltabelle
Warum besser: Der fachliche Ablauf ist erkennbar. Technische Details sitzen hinter Ports/Adaptern, Regeln sind benannt und Tests können ohne kompletten Legacy-Stack laufen.
Nachher: restartbare Step-Pipeline mit Kontrolltabelle
public final class NightlyOrderJob extends RestartableJobTemplate { // Pattern: Template Method
    private final List<JobStep> steps;

    public NightlyOrderJob(JobRepository repository, OrderImportService importService,
                           OrderBookingService bookingService, ReportService reportService) {
        super(repository);
        this.steps = List.of(
            new ValidateInputFileStep(importService),
            new ImportStagingStep(importService),      // Pattern: Idempotent Writer
            new ApplyOrdersStep(bookingService),
            new CreateReportStep(reportService),
            new ArchiveInputFileStep(importService)
        );
    }

    @Override
    protected List<JobStep> steps() {
        return steps;
    }
}

public abstract class RestartableJobTemplate {
    private final JobRepository repository;

    protected RestartableJobTemplate(JobRepository repository) {
        this.repository = repository;
    }

    public final JobReport run(JobRunId runId) {
        JobReport report = new JobReport(runId);
        for (JobStep step : steps()) {
            if (repository.isCompleted(runId, step.name())) {
                report.skipped(step.name());
                continue; // Pattern: Checkpoint
            }
            repository.markStarted(runId, step.name());
            StepResult result = step.execute(runId);
            repository.markCompleted(runId, step.name(), result.metrics());
            report.completed(step.name(), result.metrics());
        }
        return report;
    }

    protected abstract List<JobStep> steps();
}
Wirkung und Trade-offs

Wiederanlauf

Fertige Steps werden übersprungen statt wiederholt.

Fachnachweis

Pro Step sind Mengen und Fehler sichtbar.

Migration

Pipeline kann später in Spring Batch oder Kubernetes CronJob wandern.

Risiko

Dateiimport und Buchung sind voneinander getrennt.

Wichtig: Refactoring heißt nicht Big-Bang-Neuschreiben. Die alte Schnittstelle darf stabil bleiben, während innen schrittweise sauberere Strukturen entstehen.
⌂ Cockpit