Files
gala-ki-spielwiese/quarkus-automaton/docs/Architecture.md

6.2 KiB

Architektur — Dateieingang Service

Überblick

Der Service empfängt einen HTTP-Trigger von der APEX Automation, verbindet sich mit einem SFTP-Server, lädt neue ZIP-Dateien herunter, entpackt sie und lädt den Inhalt in OCI Object Storage hoch. Anschließend wird der ORDS-Endpunkt pck_auto_import.p_process_incoming_ba_data aufgerufen, der die DB-Verarbeitung anstößt. Das Scheduling bleibt bei APEX — der Service selbst hat keinen eigenen Scheduler.

Datenfluss

APEX Automation (stündlich)
     │
     ▼ HTTP POST /api/process-incoming  (Header: X-Api-Key)
FileProcessingResource
     │  202 Accepted (sofort)
     ▼
FileProcessingPipeline          [ManagedExecutor — Hintergrund-Thread]
     │
     │  für jede *.zip auf dem SFTP-Server:
     │
     ├─→ SftpService.listZipFiles()       [SSHJ]
     ├─→ SftpService.download()           [SSHJ]
     │
     ├─→ ZipExtractionService.extract()   [Apache Commons Compress]
     │       └─ ProcessingContext mit List<FileEntry>
     │
     ├─→ OciUploadService.upload()        [OCI SDK]
     │       └─ Dateien in eingang/<zip-name>/ + Marker
     │
     ├─→ SftpService.deleteRemote()       [SSHJ]
     │       └─ ZIP gelöscht (Erfolg) oder .error (Fehler)
     │
     └─→ Cleanup: lokale Dateien löschen  [immer, im finally]
     │
     │  nach allen ZIPs (einmalig):
     │
     └─→ OrdsNotificationService.notify() [MicroProfile REST Client]
              └─ POST pck_auto_import.p_process_incoming_ba_data
              │
              ▼
         Oracle DB (pck_auto_import verarbeitet alle eingang/-Unterordner)

Pipeline-Steps

Step-Name Klasse Paket Bibliothek
api-trigger FileProcessingResource api Quarkus REST
sftp-list SftpService sftp SSHJ
sftp-download SftpService sftp SSHJ
zip-extract ZipExtractionService zip Apache Commons Compress
oci-upload OciUploadService oci OCI Java SDK
sftp-rename SftpService sftp SSHJ
ords-notify OrdsNotificationService ords MicroProfile REST Client
cleanup FileProcessingPipeline pipeline pure Java

Orchestrierung

FileProcessingPipeline ist der einzige Orchestrierer — kein Framework-Routing. Die Pipeline läuft in einem ManagedExecutor-Thread (Quarkus Context Propagation), damit MDC-Kontext und CDI-Scope im Hintergrund-Thread verfügbar bleiben.

FileProcessingResource          [REST-Thread]
    │  202 sofort
    ▼
FileProcessingPipeline          [ManagedExecutor-Thread]
    │  AtomicBoolean verhindert Doppelläufe
    │
    └─ processAll()
           └─ für jede ZIP: try { ... } finally { cleanup(); MDC.clear(); }

Der AtomicBoolean isRunning-Guard stellt sicher, dass ein zweiter APEX-Trigger während eines laufenden Durchgangs mit 409 Conflict abgewiesen wird, statt einen zweiten Lauf zu starten.

Konfiguration (application.properties)

# API-Absicherung
galabau.api.key=${GALABAU_API_KEY}

# SFTP
galabau.sftp.host=sftp.lieferant.de
galabau.sftp.port=22
galabau.sftp.username=sftp_user
galabau.sftp.password=${GALABAU_SFTP_PASSWORD}
galabau.sftp.host-key-fingerprint=SHA256:...   # ssh-keyscan host | ssh-keygen -lf -
galabau.sftp.remote-path=/outgoing
galabau.sftp.local-work-dir=/tmp/sftp-work
# galabau.sftp.private-key-path=/etc/secrets/sftp-key
# galabau.sftp.private-key-passphrase=${SFTP_KEY_PASSPHRASE}

# OCI Object Storage
galabau.oci.namespace=mycompany
galabau.oci.region=eu-frankfurt-1
galabau.oci.bucket=my-bucket
galabau.oci.tenant-prefix=mandant_42/
galabau.oci.incoming-prefix=eingang/
galabau.oci.tenancy-id=${OCI_TENANCY_ID}
galabau.oci.user-id=${OCI_USER_ID}
galabau.oci.fingerprint=${OCI_FINGERPRINT}
galabau.oci.private-key-path=${OCI_PRIVATE_KEY_PATH}

# ORDS
galabau.ords.base-url=http://ords:8080
galabau.ords.process-incoming-path=/ords/.../net_storage/process_incoming_ba_data
galabau.ords.api-key=${GALABAU_ORDS_API_KEY}
quarkus.rest-client.ords-client.url=${galabau.ords.base-url}

# Retry
galabau.retry.max-attempts=3
galabau.retry.backoff-ms=1000

Fehlerbehandlung

Fehler Typ Handling
SFTP-Verbindung fehlgeschlagen transient Pipeline bricht ab, nächster APEX-Lauf (1h)
ZIP beschädigt persistent SFTP-Rename zu .error, Log, weiter mit nächster ZIP
OCI 503 transient @Retry (3x mit Backoff)
OCI 403 Auth Failed persistent SFTP-Rename zu .error, Log — Credentials prüfen!
ORDS-Aufruf fehlgeschlagen transient Marker liegt vor, APEX findet ihn nächsten Lauf

OCI-Authentifizierung

SimpleAuthenticationDetailsProvider mit Credentials aus Umgebungsvariablen (skalare Werte) und gemounteter PEM-Datei (Private Key via Kubernetes Secret Volume).

SimpleAuthenticationDetailsProvider auth =
    SimpleAuthenticationDetailsProvider.builder()
        .tenantId(config.tenancyId())
        .userId(config.userId())
        .fingerprint(config.fingerprint())
        .region(Region.fromRegionId(config.region()))
        .privateKeySupplier(new FilePrivateKeySupplier(config.privateKeyPath()))
        .build();

Datenmodell

ProcessingContext

Feld Typ Beschreibung
runId UUID Eindeutige ID dieses Verarbeitungslaufs (MDC-Kontext)
zipFilename String z.B. export_2026-04-08.zip
zipNameWithoutExt String z.B. export_2026-04-08
localZipPath Path Lokale ZIP-Datei (für Cleanup)
localExtractDir Path Lokales Entpack-Verzeichnis (für Cleanup)
extractedFiles List<FileEntry> Liste der entpackten Dateien
markerUploaded boolean Marker in OCI hochgeladen?
startTime LocalDateTime Startzeitpunkt
status ProcessingStatus PENDING / PARTIALLY_UPLOADED / MARKER_UPLOADED / ORDS_NOTIFIED / FAILED

FileEntry

Feld Typ Beschreibung
relativePath String z.B. subdir/file.csv
ociKey String z.B. eingang/export_2026-04-08/subdir/file.csv
fileSize long Dateigröße in Bytes
isMarker boolean true nur für _READY_FOR_DB_PROCESSING_