Dokumentation aktualisiert auf quarkus und besser strukturiert

This commit is contained in:
2026-04-08 14:18:07 +02:00
parent 39390e94e5
commit d9adccf63c
15 changed files with 2216 additions and 21 deletions

View File

@@ -0,0 +1,166 @@
# 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_files`
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.renameRemote() [SSHJ]
│ └─ .processed (Erfolg) oder .error (Fehler)
├─→ OrdsNotificationService.notify() [MicroProfile REST Client]
│ └─ POST pck_auto_import.p_process_incoming_files
└─→ Cleanup: lokale Dateien löschen [immer, im finally]
Oracle DB (pck_auto_import verarbeitet eingang/<zip-name>/)
```
## 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`)
```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/.../auto_import/process_incoming
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).
```java
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_` |