bdc5e8331f
Drei neue Architektur-Übersichten unter docs/architecture/ angelegt (domain-overview, gui-overview, adapter-overview), die das bisher in CLAUDE.md verstreute Detailwissen zu Paketen, Schlüsselklassen, Ports und Bootstrap-Verdrahtung pro Modulbereich bündeln. CLAUDE.md verweist auf die drei Dateien und enthält das Detailwissen nicht mehr selbst, sodass Arbeit an einem Modulbereich mit CLAUDE.md plus der jeweils passenden Übersicht auskommt. Workpackage-Liste um M14 und M15 ergänzt; V2.9-Implementierungsstand auf Modul-/Verhaltensebene konsolidiert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
320 lines
16 KiB
Markdown
320 lines
16 KiB
Markdown
# Architektur-Übersicht: Adapter-Out, CLI & Bootstrap
|
||
|
||
Diese Datei beschreibt die drei Module `pdf-umbenenner-adapter-out`, `pdf-umbenenner-adapter-in-cli`
|
||
und `pdf-umbenenner-bootstrap`: ihren Zweck, ihre Paketstruktur, die wichtigsten Klassen und die
|
||
Verdrahtungslogik beim Programmstart. Sie richtet sich an Entwickler, die in einem dieser Module
|
||
arbeiten wollen und noch keinen Überblick über das Projekt haben. Domain- und Application-Schicht
|
||
(Port-Verträge, fachliche Domänenobjekte, Use-Case-Interfaces) sind nicht Gegenstand dieses
|
||
Dokuments – sie sind in `docs/architecture/domain-overview.md` beschrieben. GUI-interne Ports und
|
||
die Struktur des GUI-Adapters finden sich in `docs/architecture/gui-overview.md`. Die hexagonale
|
||
Abhängigkeitsrichtung ist strikt: Adapter kennen Domain und Application, nicht umgekehrt. Adapter
|
||
dürfen außerdem nicht direkt voneinander abhängen.
|
||
|
||
---
|
||
|
||
## 1. Modulzweck
|
||
|
||
### pdf-umbenenner-adapter-out
|
||
|
||
Enthält alle Outbound-Adapter-Implementierungen, also die konkreten technischen Lösungen für
|
||
sämtliche Outbound-Ports der Application. Dazu gehören: Dateisystemzugriff, PDF-Textextraktion
|
||
via PDFBox, SQLite-Persistenz (Schema, Repositories, Unit of Work), HTTP-Clients für zwei
|
||
KI-Provider-Familien (OpenAI-kompatibel und Anthropic nativ), Properties-Konfiguration inklusive
|
||
Legacy-Migration, dateibasierter Run-Lock sowie Systemuhr und SHA-256-Fingerprint.
|
||
|
||
### pdf-umbenenner-adapter-in-cli
|
||
|
||
Schlanker Inbound-Adapter für den kopflosen Batch-Betrieb. Enthält genau eine Klasse
|
||
(`SchedulerBatchCommand`), die den CLI-Einstiegspunkt bildet und ausschließlich über das
|
||
Inbound-Port-Interface an die Application delegiert. Keine eigene Fachlogik.
|
||
|
||
### pdf-umbenenner-bootstrap
|
||
|
||
Composition Root der Anwendung. Verantwortlich für: CLI-Argument-Parsing,
|
||
Konfigurationsauflösung und -validierung, Aufbau des vollständigen Objektgraphen (manuell, ohne
|
||
DI-Framework), Auswahl der aktiven KI-Adapter-Implementierung, Dispatch auf GUI- oder
|
||
Headless-Pfad sowie Exit-Code-Ableitung. Bootstrap ist die einzige Stelle, an der alle Module
|
||
zusammengeführt werden.
|
||
|
||
---
|
||
|
||
## 2. Paketstruktur
|
||
|
||
### pdf-umbenenner-adapter-out
|
||
|
||
Wurzelpaket: `de.gecheckt.pdf.umbenenner.adapter.out`
|
||
|
||
| Unterpaket | Inhalt |
|
||
|-------------------------|-------------------------------------------------------------------------------------|
|
||
| `.ai` | HTTP-Adapter für OpenAI-kompatible Schnittstelle und Anthropic Messages API |
|
||
| `.clock` | Systemuhr-Adapter (`Instant.now()`) |
|
||
| `.configuration` | Properties-Laden, Multi-Provider-Parsing/-Validierung, Legacy-Migration |
|
||
| `.fingerprint` | SHA-256-Inhalts-Fingerprint |
|
||
| `.lock` | Dateibasierter Run-Lock |
|
||
| `.modelcatalog` | HTTP-Modellabruf für den GUI-Konfigurationseditor |
|
||
| `.pathcheck` | Pfadprüfung für den GUI-Editor |
|
||
| `.pdfextraction` | PDFBox-3.x-Adapter: Textextraktion und Seitenanzahl |
|
||
| `.prompt` | Prompt-Template-Lader |
|
||
| `.resourcecreation` | Anlegen von Ordnern und Dateien (korrigierende technische Tests) |
|
||
| `.sourcedocument` | Quellordner-Scanner (nicht rekursiv) |
|
||
| `.sqlite` | Schema-Initialisierung, Repositories, Unit of Work |
|
||
| `.targetcopy` | Zielkopie via Temp-Datei und atomarem Move |
|
||
| `.targetfolder` | Kollisionsfreier Zieldateiname, Umbenennung bestehender Zieldateien |
|
||
| `.validation` | API-Key-Auflösung aus Umgebungsvariablen (GUI-Editor) |
|
||
| `.bootstrap.validation` | `StartConfiguration`-Validierung vor Prozessstart |
|
||
|
||
### pdf-umbenenner-adapter-in-cli
|
||
|
||
Wurzelpaket: `de.gecheckt.pdf.umbenenner.adapter.in.cli`
|
||
|
||
Enthält ausschließlich `SchedulerBatchCommand` sowie die zugehörige `package-info.java`.
|
||
|
||
### pdf-umbenenner-bootstrap
|
||
|
||
Wurzelpaket: `de.gecheckt.pdf.umbenenner.bootstrap`
|
||
|
||
| Unterpaket | Inhalt |
|
||
|---------------------|-------------------------------------------------------------------------------------------------------|
|
||
| *(Wurzel)* | `PdfUmbenennerApplication` (main), `BootstrapRunner`, `AiProviderSelector` |
|
||
| `.adapter` | Bootstrap-interne Adapter: `Log4jProcessingLogger`, `GuiConfigurationPropertiesWriter`, `AiModelCatalogDispatcher` |
|
||
| `.singleinstance` | `SingleInstanceGuard` – Einzelinstanz-Schutz via Loopback-ServerSocket |
|
||
| `.startup` | `StartupMode`, `StartupArguments`, `CliArgumentParser` |
|
||
|
||
---
|
||
|
||
## 3. Schlüsselklassen
|
||
|
||
Die folgenden Klassen sind für das Verständnis der drei Module zentral. FQN-Kürzel: `...` steht
|
||
jeweils für das Wurzelpaket des Moduls.
|
||
|
||
### Adapter-Out
|
||
|
||
#### KI-Adapter
|
||
|
||
- **`...ai.OpenAiHttpAdapter`** – implementiert `AiInvocationPort` für OpenAI-kompatible Endpunkte.
|
||
POST `{baseUrl}/v1/chat/completions`, Bearer-Authentifizierung, extrahiert
|
||
`choices[0].message.content`, klassifiziert HTTP-Fehler und Timeouts als
|
||
`AiInvocationTechnicalFailure`.
|
||
|
||
- **`...ai.AnthropicClaudeHttpAdapter`** – implementiert `AiInvocationPort` für die native
|
||
Anthropic Messages API. POST `/v1/messages`, Header `x-api-key` und `anthropic-version`,
|
||
konkateniert `text`-Content-Blöcke aus dem Antwort-Array.
|
||
|
||
Beide Adapter liefern denselben Domain-Typ (`NamingProposal`) und enthalten keinerlei
|
||
provider-spezifische Typen in öffentlichen Signaturen. Welche Implementierung aktiv ist, entscheidet
|
||
ausschließlich der Bootstrap (→ `AiProviderSelector`).
|
||
|
||
#### Modell-Katalog (GUI)
|
||
|
||
- **`...modelcatalog.ClaudeModelCatalogAdapter`** – `AiModelCatalogPort` für Claude,
|
||
GET `/v1/models` mit `x-api-key`.
|
||
|
||
- **`...modelcatalog.OpenAiCompatibleModelCatalogAdapter`** – `AiModelCatalogPort` für
|
||
OpenAI-kompatibel, GET `/v1/models` mit Bearer.
|
||
|
||
#### PDF-Extraktion
|
||
|
||
- **`...pdfextraction.PdfTextExtractionPortAdapter`** – PDFBox-3.x-Adapter. Alle technischen
|
||
Fehler werden als `PdfExtractionTechnicalError` zurückgegeben; es werden keine Exceptions
|
||
propagiert.
|
||
|
||
#### SQLite
|
||
|
||
- **`...sqlite.SqliteSchemaInitializationAdapter`** – legt Tabellen `document_record` und
|
||
`processing_attempt` an. Schema-Evolution erfolgt per `ALTER TABLE ADD COLUMN`; bestehende
|
||
Datenbestände bleiben rückwärtskompatibel.
|
||
|
||
- **`...sqlite.SqliteUnitOfWorkAdapter`** – implementiert `UnitOfWorkPort`. Setzt
|
||
`autoCommit=false`, führt atomare Commits durch, rollt bei Fehlern zurück.
|
||
|
||
- **`...sqlite.SqliteDocumentRecordRepositoryAdapter`** – Stammsatz pro SHA-256-Fingerprint
|
||
(Gesamtstatus, Fehlerzähler, Zieldateiname usw.).
|
||
|
||
- **`...sqlite.SqliteProcessingAttemptRepositoryAdapter`** – Versuchshistorie, referenziert
|
||
über Fingerprint. Enthält u. a. Provider-Identifikator, Modellname, Prompt-Identifikator,
|
||
KI-Rohantwort und finalen Zieldateinamen.
|
||
|
||
#### Konfiguration
|
||
|
||
- **`...configuration.PropertiesConfigurationPortAdapter`** – implementiert `ConfigurationPort`.
|
||
Lädt `config/application.properties` (oder einen `--config`-Override), parst via
|
||
`MultiProviderConfigurationParser`, löst API-Keys aus Umgebungsvariablen
|
||
(`OPENAI_COMPATIBLE_API_KEY`, `ANTHROPIC_API_KEY`).
|
||
|
||
- **`...configuration.LegacyConfigurationMigrator`** – erkennt alte Flat-Key-Konfigurationen
|
||
(Schlüssel wie `api.baseUrl`, `api.model`), legt eine `.bak`-Sicherung an und überführt den
|
||
Inhalt in das aktuelle Multi-Provider-Schema.
|
||
|
||
#### Laufzeitinfrastruktur
|
||
|
||
- **`...lock.FilesystemRunLockPortAdapter`** – Lock-Datei mit PID-Inhalt. Wirft
|
||
`RunLockUnavailableException`, wenn die Datei bereits vorhanden ist. Release löscht die Datei
|
||
(best-effort).
|
||
|
||
- **`...clock.SystemClockAdapter`** – delegiert an `Instant.now()`.
|
||
|
||
- **`...fingerprint.Sha256FingerprintAdapter`** – SHA-256 über den Rohdatei-Inhalt. Fehler als
|
||
`FingerprintTechnicalError`.
|
||
|
||
#### Zieldatei
|
||
|
||
- **`...targetcopy.FilesystemTargetFileCopyAdapter`** – kopiert die Quelldatei zunächst in eine
|
||
`.tmp`-Datei, dann atomarer Move (Fallback: Standard-Move). Die Quelldatei wird in keinem Fall
|
||
verändert.
|
||
|
||
- **`...targetfolder.FilesystemTargetFolderAdapter`** – ermittelt einen kollisionsfreien
|
||
Zieldateinamen mit `(1)`, `(2)`-Suffix. Erkennt inhaltsidentische Duplikate via SHA-256.
|
||
|
||
#### Validierung vor Prozessstart
|
||
|
||
- **`...bootstrap.validation.StartConfigurationValidator`** – validiert die geladene
|
||
`StartConfiguration` auf Pflichtfelder, Wertebereiche, URI-Syntax und Pfadbedingungen.
|
||
Wird im Bootstrap-Headless-Pfad unmittelbar nach dem Laden der Konfiguration aufgerufen.
|
||
|
||
---
|
||
|
||
### Adapter-In-CLI
|
||
|
||
- **`...adapter.in.cli.SchedulerBatchCommand`** – einziger Inbound-Adapter für den Headless-Betrieb.
|
||
Nimmt einen `BatchRunContext` entgegen, delegiert an `BatchRunProcessingUseCase.execute()` und
|
||
gibt `BatchRunOutcome` zurück. Enthält keine eigene Fachlogik; die Verdrahtung mit dem
|
||
Use-Case-Interface erfolgt ausschließlich im Bootstrap.
|
||
|
||
---
|
||
|
||
### Bootstrap
|
||
|
||
- **`...bootstrap.PdfUmbenennerApplication`** – `main`-Methode. Parst CLI-Argumente via
|
||
`CliArgumentParser`, bricht bei ungültiger Verwendung mit Exit-Code 1 ab, delegiert an
|
||
`BootstrapRunner.run()` und ruft abschließend `System.exit()` mit dem zurückgegebenen Code auf.
|
||
|
||
- **`...bootstrap.BootstrapRunner`** – Herzstück der Verdrahtung. Baut den Objektgraph für
|
||
Headless- und GUI-Pfad, dispatcht über `StartupMode`, enthält `buildProductionBatchUseCase()`
|
||
und `runHeadlessBatch()` als zentrale Kompositionsmethoden, liefert den Exit-Code zurück.
|
||
|
||
- **`...bootstrap.AiProviderSelector`** – einzige Stelle, an der `AiProviderFamily` auf eine
|
||
konkrete `AiInvocationPort`-Implementierung abgebildet wird:
|
||
`OPENAI_COMPATIBLE` → `OpenAiHttpAdapter`, `CLAUDE` → `AnthropicClaudeHttpAdapter`.
|
||
|
||
- **`...bootstrap.startup.CliArgumentParser`** – parst `--headless` und `--config <Pfad>` zu einem
|
||
typsicheren `StartupArgumentsParseResult` (sealed: `Valid` / `Invalid`).
|
||
|
||
- **`...bootstrap.singleinstance.SingleInstanceGuard`** – bindet einen Loopback-ServerSocket auf
|
||
Port 47832. Wirft `AnotherInstanceRunningException`, wenn der Port bereits belegt ist. Ein
|
||
Shutdown-Hook gibt den Socket frei.
|
||
|
||
- **`...bootstrap.adapter.AiModelCatalogDispatcher`** – Bootstrap-interner Dispatcher für die GUI.
|
||
Routet `AiModelCatalogPort`-Aufrufe anhand des `providerIdentifier` an den Claude- oder
|
||
OpenAI-kompatiblen Modell-Katalog-Adapter. Thread-safe.
|
||
|
||
- **`...bootstrap.adapter.Log4jProcessingLogger`** – implementiert `ProcessingLogger` auf Basis
|
||
von Log4j2. Unterdrückt sensitive KI-Inhalte, wenn `AiContentSensitivity.PROTECT_SENSITIVE_CONTENT`
|
||
gesetzt ist.
|
||
|
||
- **`...bootstrap.adapter.GuiConfigurationPropertiesWriter`** – schreibt die im GUI-Editor
|
||
bearbeitete Konfiguration als normalisierte `application.properties` zurück auf das Dateisystem.
|
||
|
||
---
|
||
|
||
## 4. Verdrahtungslogik in Bootstrap
|
||
|
||
Die folgende Sequenz beschreibt den Ablauf von `main()` bis zum Start des eigentlichen Adapters.
|
||
Der Objektgraph wird ausschließlich durch manuelle `new`-Aufrufe aufgebaut; es wird kein
|
||
DI-Framework verwendet.
|
||
|
||
**Argument-Parsing**
|
||
- `PdfUmbenennerApplication.main()` → `CliArgumentParser.parse(args)`
|
||
- Ergebnis `Invalid` → Exit-Code 1, keine weiteren Schritte
|
||
|
||
**Einzelinstanz-Schutz**
|
||
- `BootstrapRunner.run()` → `SingleInstanceGuard.acquire()`
|
||
- `AnotherInstanceRunningException` → Exit-Code 1; im GUI-Modus zusätzlich ein Swing-Warndialog
|
||
|
||
**Modus-Dispatch**
|
||
- `BootstrapRunner.run()` wertet `startupArguments.mode()` aus:
|
||
- `HEADLESS` → `runHeadlessBatch()`
|
||
- `GUI` → `startGuiMode()`
|
||
|
||
**Konfigurationsauflösung (Headless-Pfad)**
|
||
- Prüfung, ob `--config`-Datei existiert (Fehler → Exit-Code 1)
|
||
- `LegacyConfigurationMigrator.migrateIfLegacy()` bei erkannter Legacy-Form
|
||
- `PropertiesConfigurationPortAdapter` lädt und parst die Properties
|
||
- `StartConfigurationValidator` validiert die geladene `StartConfiguration`
|
||
- Validierungsfehler → Exit-Code 1
|
||
|
||
**KI-Provider-Auswahl**
|
||
- Innerhalb von `buildProductionBatchUseCase()`:
|
||
`multiProviderConfiguration().activeProviderFamily()` → `AiProviderSelector.select(family, providerConfig)`
|
||
- Ergebnis: genau eine `AiInvocationPort`-Instanz
|
||
|
||
**Objektgraph-Aufbau (Headless)**
|
||
- Erzeugte Instanzen (Reihenfolge nach Abhängigkeit): `Sha256FingerprintAdapter`,
|
||
`SqliteDocumentRecordRepositoryAdapter`, `SqliteProcessingAttemptRepositoryAdapter`,
|
||
`SqliteUnitOfWorkAdapter`, `FilesystemTargetFolderAdapter`, `FilesystemTargetFileCopyAdapter`,
|
||
`FilesystemPromptPortAdapter`, `SystemClockAdapter`, `SourceDocumentCandidatesPortAdapter`,
|
||
`PdfTextExtractionPortAdapter`, `Log4jProcessingLogger`
|
||
- Application-Services (`DocumentProcessingCoordinator`, `AiResponseValidator`,
|
||
`AiNamingService`) werden verdrahtet und in `DefaultBatchRunProcessingUseCase` eingebettet
|
||
|
||
**CLI-Adapter**
|
||
- `BootstrapRunner` erzeugt `SchedulerBatchCommand` mit dem fertigen `BatchRunProcessingUseCase`
|
||
|
||
**Exit-Code-Ableitung**
|
||
- `BatchRunOutcome` → 0 (Lauf technisch erfolgreich) oder 1 (harter Bootstrap-/Konfigurationsfehler)
|
||
- `PdfUmbenennerApplication` ruft `System.exit(exitCode)` auf
|
||
|
||
**GUI-Pfad**
|
||
- `startGuiMode()` baut via `buildGuiStartupContext()` einen `GuiStartupContext`:
|
||
enthält `AiModelCatalogDispatcher`, `EnvironmentApiKeyResolutionAdapter`,
|
||
`TechnicalTestOrchestrator`, `GuiConfigurationPropertiesWriter`
|
||
- `GuiAdapter.start(context)` übernimmt; ab diesem Punkt liegt die Kontrolle beim GUI-Adapter
|
||
- Im GUI-Pfad: keine SQLite-Schema-Initialisierung, kein Run-Lock-Erwerb, kein Batch-Use-Case
|
||
- GUI-interne Ports und deren Verbindung mit Outbound-Adaptern sind in
|
||
`docs/architecture/gui-overview.md` beschrieben
|
||
|
||
---
|
||
|
||
## 5. Einstiegspunkte je Modul
|
||
|
||
### pdf-umbenenner-adapter-out
|
||
|
||
1. **`...ai.OpenAiHttpAdapter`** – zeigt das typische Adapter-Muster: Port-Interface implementieren,
|
||
alle provider-spezifischen Details kapseln, `ProviderConfiguration` als einzige
|
||
Konfigurationsquelle konsumieren. Danach `AnthropicClaudeHttpAdapter` zum Vergleich lesen.
|
||
|
||
2. **`...sqlite.SqliteSchemaInitializationAdapter`** – erklärt das Datenbankschema, das alle
|
||
SQLite-Adapter voraussetzen. Hier sieht man, welche Felder in `document_record` und
|
||
`processing_attempt` existieren und wie Schema-Evolution additiv umgesetzt ist.
|
||
|
||
3. **`...configuration.PropertiesConfigurationPortAdapter`** – Einstieg in die
|
||
Konfigurationskette. Von hier aus `MultiProviderConfigurationParser` und
|
||
`LegacyConfigurationMigrator` nachverfolgen.
|
||
|
||
### pdf-umbenenner-adapter-in-cli
|
||
|
||
1. **`...adapter.in.cli.SchedulerBatchCommand`** – komprimiertes Inbound-Adapter-Muster in einer
|
||
einzigen Klasse. Zeigt, wie ein Inbound-Adapter ausschließlich über Port-Interfaces mit der
|
||
Application kommuniziert.
|
||
|
||
2. **`package-info.java`** – beschreibt Abhängigkeitsrichtung und Verdrahtungsvertrag dieses
|
||
Adapters.
|
||
|
||
3. **`SchedulerBatchCommandTest`** – zeigt, wie der Adapter ohne Bootstrap testbar ist.
|
||
|
||
### pdf-umbenenner-bootstrap
|
||
|
||
1. **`PdfUmbenennerApplication`** – Startpunkt; die kurze Kette von `main()` bis `System.exit()`
|
||
gibt einen ersten Überblick über die gesamte Startsequenz.
|
||
|
||
2. **`BootstrapRunner`** – Herzstück; `buildProductionBatchUseCase()` zeigt, wie der vollständige
|
||
Objektgraph manuell aufgebaut wird. `runHeadlessBatch()` zeigt den Headless-Kontrollfluss.
|
||
|
||
3. **`AiProviderSelector`** – kleinste Klasse mit größter Hebelwirkung: hier liegt die einzige
|
||
Stelle, an der die Provider-Auswahl aus der Konfiguration auf eine konkrete
|
||
`AiInvocationPort`-Implementierung trifft.
|
||
|
||
---
|
||
|
||
*Port-Verträge und Domain-Typen: `docs/architecture/domain-overview.md`*
|
||
*GUI-interne Ports und GUI-Adapter-Struktur: `docs/architecture/gui-overview.md`*
|