1
0

M4 AP-006 Rollback-Semantik und Bootstrap-Scope bereinigen

This commit is contained in:
2026-04-03 09:32:47 +02:00
parent d61299f892
commit 30f070f2a6
3 changed files with 214 additions and 55 deletions

View File

@@ -43,8 +43,7 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
* <ol>
* <li>Load and validate the startup configuration.</li>
* <li>Resolve the run-lock file path (with default fallback).</li>
* <li>Initialise the SQLite schema (M4: before the batch document loop begins).</li>
* <li>Create and wire all ports and adapters, including the M4 persistence ports.</li>
* <li>Create and wire all ports and adapters via configured factories.</li>
* <li>Start the CLI adapter and execute the batch use case.</li>
* <li>Map the batch outcome to a process exit code.</li>
* </ol>
@@ -54,17 +53,17 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
* <li>{@code 0}: Batch run executed successfully; individual document failures do not
* change the exit code as long as the run itself completed without a hard
* infrastructure error.</li>
* <li>{@code 1}: Hard start, bootstrap, configuration, or schema-initialisation failure
* <li>{@code 1}: Hard start, bootstrap, configuration, or persistence failure
* that prevented the run from beginning, or a critical infrastructure failure
* during the run.</li>
* </ul>
*
* <h2>M4 wiring</h2>
* <p>
* The production constructor wires the following M4 adapters:
* The production constructor wires the following M4 adapters via the UseCaseFactory:
* <ul>
* <li>{@link Sha256FingerprintAdapter} — SHA-256 content fingerprinting.</li>
* <li>{@link SqliteSchemaInitializationAdapter} — schema initialisation at startup.</li>
* <li>{@link SqliteSchemaInitializationAdapter} — schema initialisation (AP-007).</li>
* <li>{@link SqliteDocumentRecordRepositoryAdapter} — document master record CRUD.</li>
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} — attempt history CRUD.</li>
* <li>{@link SqliteUnitOfWorkAdapter} — atomic persistence operations.</li>
@@ -141,8 +140,8 @@ public class BootstrapRunner {
* <li>{@link SqliteUnitOfWorkAdapter} for atomic persistence operations.</li>
* </ul>
* <p>
* Schema initialisation is performed in {@link #run()} before the use case is created,
* using {@link SqliteSchemaInitializationAdapter}.
* Schema initialisation is performed by the UseCaseFactory when the use case is created,
* using {@link SqliteSchemaInitializationAdapter}. (AP-007 responsibility)
*/
public BootstrapRunner() {
this.configPortFactory = PropertiesConfigurationPortAdapter::new;
@@ -150,6 +149,11 @@ public class BootstrapRunner {
this.validatorFactory = StartConfigurationValidator::new;
this.useCaseFactory = (config, lock) -> {
String jdbcUrl = buildJdbcUrl(config);
// AP-007: Initialize schema when the use case is created
if (config.sqliteFile() != null) {
PersistenceSchemaInitializationPort schemaPort = new SqliteSchemaInitializationAdapter(jdbcUrl);
schemaPort.initializeSchema();
}
FingerprintPort fingerprintPort = new Sha256FingerprintAdapter();
DocumentRecordRepository documentRecordRepository =
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
@@ -197,14 +201,11 @@ public class BootstrapRunner {
* M4 additions:
* <ul>
* <li>Derives the SQLite JDBC URL from the configured {@code sqlite.file} path.</li>
* <li>Initialises the M4 SQLite schema via
* {@link PersistenceSchemaInitializationPort#initializeSchema()} before the
* batch document loop begins. A schema initialisation failure aborts the run
* with exit code&nbsp;1.</li>
* <li>Creates the M4-aware use case via the {@link UseCaseFactory}, which handles
* SQLite schema initialisation and wiring of persistence ports.</li>
* </ul>
*
* @return exit code: 0 for success, 1 for invalid configuration, schema failure,
* or unexpected bootstrap failure
* @return exit code: 0 for success, 1 for invalid configuration or unexpected bootstrap failure
*/
public int run() {
LOG.info("Bootstrap flow started.");
@@ -228,24 +229,21 @@ public class BootstrapRunner {
}
RunLockPort runLockPort = runLockPortFactory.create(lockFilePath);
// Step 5 (M4): Initialise the SQLite schema before the batch loop begins.
// A failure here is a hard start error → exit code 1.
initializeSchema(config);
// Step 6: Create the batch run context
// Step 5: Create the batch run context
RunId runId = new RunId(UUID.randomUUID().toString());
BatchRunContext runContext = new BatchRunContext(runId, Instant.now());
LOG.info("Batch run started. RunId: {}", runId);
// Step 7: Create the use case with the validated config and run lock.
// Step 6: Create the use case with the validated config and run lock.
// Config is passed directly; the use case does not re-read the properties file.
// Adapters (source document port, PDF extraction port, M4 ports) are wired by the factory.
// Schema initialization happens within the UseCaseFactory. (AP-007 responsibility)
BatchRunProcessingUseCase useCase = useCaseFactory.create(config, runLockPort);
// Step 8: Create the CLI command adapter with the use case
// Step 7: Create the CLI command adapter with the use case
SchedulerBatchCommand command = commandFactory.create(useCase);
// Step 9: Execute the command with the run context and handle the outcome
// Step 8: Execute the command with the run context and handle the outcome
BatchRunOutcome outcome = command.run(runContext);
// Mark run as completed
@@ -271,8 +269,9 @@ public class BootstrapRunner {
LOG.error("Configuration loading failed: {}", e.getMessage());
return 1;
} catch (DocumentPersistenceException e) {
// Schema initialisation failed hard start error
LOG.error("SQLite schema initialisation failed: {}", e.getMessage(), e);
// Persistence operation during startup failed hard start error
// (e.g., schema initialisation in UseCaseFactory)
LOG.error("Persistence operation during startup failed: {}", e.getMessage(), e);
return 1;
} catch (Exception e) {
LOG.error("Bootstrap failure during startup.", e);
@@ -280,33 +279,6 @@ public class BootstrapRunner {
}
}
/**
* Initialises the M4 SQLite schema using the configured SQLite file path.
* <p>
* This method is called once at startup, before the batch document loop begins.
* It uses the production {@link SqliteSchemaInitializationAdapter} directly because
* schema initialisation is a startup concern, not a per-document concern, and the
* {@link UseCaseFactory} abstraction is not the right place for it.
* <p>
* If the {@code sqlite.file} configuration is null or blank, schema initialisation
* is skipped with a warning. This allows the existing test infrastructure (which
* uses the custom {@link UseCaseFactory}) to continue working without a real SQLite
* file.
*
* @param config the validated startup configuration
* @throws DocumentPersistenceException if schema initialisation fails
*/
private void initializeSchema(StartConfiguration config) {
if (config.sqliteFile() == null) {
LOG.warn("sqlite.file not configured skipping schema initialisation.");
return;
}
String jdbcUrl = buildJdbcUrl(config);
PersistenceSchemaInitializationPort schemaPort = new SqliteSchemaInitializationAdapter(jdbcUrl);
schemaPort.initializeSchema();
LOG.info("M4 SQLite schema initialised at: {}", jdbcUrl);
}
/**
* Builds the JDBC URL for the SQLite database from the configured file path.
*