From 15e532eb965ace9676ea5e193a2bf5eb79623305 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 14 Apr 2026 15:17:57 +0200 Subject: [PATCH] refactor(ocr): extract assertNoRunningTraining() to eliminate duplicate guard Co-Authored-By: Claude Sonnet 4.6 --- .../service/OcrTrainingService.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java index 820ab2f5..dc6ff043 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java @@ -45,6 +45,13 @@ public class OcrTrainingService { List runs ) {} + private void assertNoRunningTraining() { + if (trainingRunRepository.findFirstByStatus(TrainingStatus.RUNNING).isPresent()) { + throw DomainException.conflict(ErrorCode.TRAINING_ALREADY_RUNNING, + "A training run is already in progress"); + } + } + // Not safe for horizontal scaling: training reloads the Kraken model in-process on the // Python OCR service after each run. The DB-level RUNNING constraint (V30 partial unique // index) prevents concurrent training API calls, but cannot prevent two OCR service replicas @@ -53,10 +60,7 @@ public class OcrTrainingService { // Short transaction: guard check + create RUNNING row, then commit immediately. // The DB connection is released before the OCR HTTP call, which can take several minutes. OcrTrainingRun run = Objects.requireNonNull(txTemplate.execute(status -> { - if (trainingRunRepository.findFirstByStatus(TrainingStatus.RUNNING).isPresent()) { - throw DomainException.conflict(ErrorCode.TRAINING_ALREADY_RUNNING, - "A training run is already in progress"); - } + assertNoRunningTraining(); var eligibleBlocks = trainingDataExportService.queryEligibleBlocks(); if (eligibleBlocks.size() < 5) { @@ -120,10 +124,7 @@ public class OcrTrainingService { public OcrTrainingRun triggerSegTraining(UUID triggeredBy) { // Same pattern as triggerTraining: narrow transactions around DB writes only. OcrTrainingRun run = Objects.requireNonNull(txTemplate.execute(status -> { - if (trainingRunRepository.findFirstByStatus(TrainingStatus.RUNNING).isPresent()) { - throw DomainException.conflict(ErrorCode.TRAINING_ALREADY_RUNNING, - "A training run is already in progress"); - } + assertNoRunningTraining(); var segBlocks = segmentationTrainingExportService.querySegmentationBlocks(); if (segBlocks.size() < 5) {