From 0e58cbfacf36bbd0786f109834c166f267c3883d Mon Sep 17 00:00:00 2001 From: Sebastian Frick Date: Wed, 25 Feb 2026 21:43:53 +0100 Subject: [PATCH] =?UTF-8?q?fix(infra):=20no-db=20Profil=20robuster=20mache?= =?UTF-8?q?n=20und=20Stub-Beans=20erg=C3=A4nzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DatabaseProfileInitializer setzt Autoconfigure-Exclusions programmatisch, da application-no-db.yml bei spätem Profil-Aktivierung nicht geladen wird. SpringUnitOfWork mit @Profile("!no-db") ausgeschlossen. Stub-Beans für BatchNumberGenerator, BatchRepository, ProductionOrderRepository, StockMovementRepository und UnitOfWork ergänzt. generate-openapi.sh nutzt korrektes -Dspring-boot.run.profiles=no-db. --- .../config/DatabaseProfileInitializer.java | 31 +++++++- .../config/SpringUnitOfWork.java | 2 + .../stub/StubBatchNumberGenerator.java | 20 +++++ .../stub/StubBatchRepository.java | 79 +++++++++++++++++++ .../stub/StubProductionOrderRepository.java | 35 ++++++++ .../stub/StubStockMovementRepository.java | 74 +++++++++++++++++ .../infrastructure/stub/StubUnitOfWork.java | 18 +++++ scripts/generate-openapi.sh | 2 +- 8 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchNumberGenerator.java create mode 100644 backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchRepository.java create mode 100644 backend/src/main/java/de/effigenix/infrastructure/stub/StubProductionOrderRepository.java create mode 100644 backend/src/main/java/de/effigenix/infrastructure/stub/StubStockMovementRepository.java create mode 100644 backend/src/main/java/de/effigenix/infrastructure/stub/StubUnitOfWork.java diff --git a/backend/src/main/java/de/effigenix/infrastructure/config/DatabaseProfileInitializer.java b/backend/src/main/java/de/effigenix/infrastructure/config/DatabaseProfileInitializer.java index 1088ed5..b32c0aa 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/config/DatabaseProfileInitializer.java +++ b/backend/src/main/java/de/effigenix/infrastructure/config/DatabaseProfileInitializer.java @@ -5,21 +5,41 @@ import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; import java.sql.DriverManager; +import java.util.Map; /** * Prüft vor dem Context-Start, ob die Datenbank erreichbar ist. * Schlägt die Verbindung fehl, wird das Spring-Profil "no-db" aktiviert. * Damit werden JPA, DataSource und Liquibase ausgeschlossen und Stub-Beans registriert. + * + * WICHTIG: Die Autoconfiguration-Exclusions werden hier programmatisch gesetzt, + * weil application-no-db.yml nicht geladen wird, wenn das Profil erst im + * ApplicationContextInitializer (nach prepareEnvironment) hinzugefügt wird. */ public class DatabaseProfileInitializer implements ApplicationContextInitializer { private static final Logger log = LoggerFactory.getLogger(DatabaseProfileInitializer.class); + private static final String AUTOCONFIGURE_EXCLUDE = String.join(",", + "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration", + "org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration", + "org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration", + "org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration", + "org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration"); + @Override public void initialize(ConfigurableApplicationContext ctx) { - var env = ctx.getEnvironment(); + var env = (ConfigurableEnvironment) ctx.getEnvironment(); + + if (env.acceptsProfiles(org.springframework.core.env.Profiles.of("no-db"))) { + log.info("Profil 'no-db' bereits aktiv – überspringe DB-Check."); + applyNoDbProperties(env); + return; + } + var url = env.getProperty("spring.datasource.url", "jdbc:postgresql://localhost:5432/effigenix"); var user = env.getProperty("spring.datasource.username", "effigenix"); var pass = env.getProperty("spring.datasource.password", "effigenix"); @@ -28,7 +48,14 @@ public class DatabaseProfileInitializer implements ApplicationContextInitializer log.debug("Datenbankverbindung erfolgreich – normaler Start."); } catch (Exception e) { log.warn("⚠️ Keine Datenbankverbindung – Stub-Modus aktiv. Einige Features sind nicht verfügbar. ({})", e.getMessage()); - ((ConfigurableEnvironment) env).addActiveProfile("no-db"); + env.addActiveProfile("no-db"); + applyNoDbProperties(env); } } + + private void applyNoDbProperties(ConfigurableEnvironment env) { + env.getPropertySources().addFirst(new MapPropertySource("no-db-overrides", Map.of( + "spring.autoconfigure.exclude", AUTOCONFIGURE_EXCLUDE + ))); + } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/config/SpringUnitOfWork.java b/backend/src/main/java/de/effigenix/infrastructure/config/SpringUnitOfWork.java index 9bc5f7f..939b4e9 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/config/SpringUnitOfWork.java +++ b/backend/src/main/java/de/effigenix/infrastructure/config/SpringUnitOfWork.java @@ -2,6 +2,7 @@ package de.effigenix.infrastructure.config; import de.effigenix.shared.common.Result; import de.effigenix.shared.persistence.UnitOfWork; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.DefaultTransactionDefinition; @@ -9,6 +10,7 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; import java.util.function.Supplier; @Component +@Profile("!no-db") public class SpringUnitOfWork implements UnitOfWork { private final PlatformTransactionManager txManager; diff --git a/backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchNumberGenerator.java b/backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchNumberGenerator.java new file mode 100644 index 0000000..91f4d86 --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchNumberGenerator.java @@ -0,0 +1,20 @@ +package de.effigenix.infrastructure.stub; + +import de.effigenix.domain.production.BatchError; +import de.effigenix.domain.production.BatchNumber; +import de.effigenix.domain.production.BatchNumberGenerator; +import de.effigenix.shared.common.Result; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; + +@Component +@Profile("no-db") +public class StubBatchNumberGenerator implements BatchNumberGenerator { + + @Override + public Result generateNext(LocalDate date) { + return Result.failure(new BatchError.RepositoryFailure("Stub-Modus: keine Datenbankverbindung")); + } +} diff --git a/backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchRepository.java b/backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchRepository.java new file mode 100644 index 0000000..bfbbfcb --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/stub/StubBatchRepository.java @@ -0,0 +1,79 @@ +package de.effigenix.infrastructure.stub; + +import de.effigenix.domain.production.Batch; +import de.effigenix.domain.production.BatchId; +import de.effigenix.domain.production.BatchNumber; +import de.effigenix.domain.production.BatchRepository; +import de.effigenix.domain.production.BatchStatus; +import de.effigenix.domain.production.RecipeId; +import de.effigenix.shared.common.RepositoryError; +import de.effigenix.shared.common.Result; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +@Repository +@Profile("no-db") +public class StubBatchRepository implements BatchRepository { + + private static final RepositoryError.DatabaseError STUB_ERROR = + new RepositoryError.DatabaseError("Stub-Modus: keine Datenbankverbindung"); + + @Override + public Result> findById(BatchId id) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAll() { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findByBatchNumber(BatchNumber batchNumber) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findByStatus(BatchStatus status) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findByProductionDate(LocalDate date) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findByRecipeIds(List recipeIds) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllSummary() { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findByStatusSummary(BatchStatus status) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findByProductionDateSummary(LocalDate date) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findByRecipeIdsSummary(List recipeIds) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result save(Batch batch) { + return Result.failure(STUB_ERROR); + } +} diff --git a/backend/src/main/java/de/effigenix/infrastructure/stub/StubProductionOrderRepository.java b/backend/src/main/java/de/effigenix/infrastructure/stub/StubProductionOrderRepository.java new file mode 100644 index 0000000..dc0504a --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/stub/StubProductionOrderRepository.java @@ -0,0 +1,35 @@ +package de.effigenix.infrastructure.stub; + +import de.effigenix.domain.production.ProductionOrder; +import de.effigenix.domain.production.ProductionOrderId; +import de.effigenix.domain.production.ProductionOrderRepository; +import de.effigenix.shared.common.RepositoryError; +import de.effigenix.shared.common.Result; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +@Profile("no-db") +public class StubProductionOrderRepository implements ProductionOrderRepository { + + private static final RepositoryError.DatabaseError STUB_ERROR = + new RepositoryError.DatabaseError("Stub-Modus: keine Datenbankverbindung"); + + @Override + public Result> findById(ProductionOrderId id) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAll() { + return Result.failure(STUB_ERROR); + } + + @Override + public Result save(ProductionOrder productionOrder) { + return Result.failure(STUB_ERROR); + } +} diff --git a/backend/src/main/java/de/effigenix/infrastructure/stub/StubStockMovementRepository.java b/backend/src/main/java/de/effigenix/infrastructure/stub/StubStockMovementRepository.java new file mode 100644 index 0000000..64e8a45 --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/stub/StubStockMovementRepository.java @@ -0,0 +1,74 @@ +package de.effigenix.infrastructure.stub; + +import de.effigenix.domain.inventory.MovementType; +import de.effigenix.domain.inventory.StockId; +import de.effigenix.domain.inventory.StockMovement; +import de.effigenix.domain.inventory.StockMovementId; +import de.effigenix.domain.inventory.StockMovementRepository; +import de.effigenix.domain.masterdata.ArticleId; +import de.effigenix.shared.common.RepositoryError; +import de.effigenix.shared.common.Result; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; + +@Repository +@Profile("no-db") +public class StubStockMovementRepository implements StockMovementRepository { + + private static final RepositoryError.DatabaseError STUB_ERROR = + new RepositoryError.DatabaseError("Stub-Modus: keine Datenbankverbindung"); + + @Override + public Result> findById(StockMovementId id) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAll() { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllByStockId(StockId stockId) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllByArticleId(ArticleId articleId) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllByMovementType(MovementType movementType) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllByBatchReference(String batchReference) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllByPerformedAtBetween(Instant from, Instant to) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllByPerformedAtAfter(Instant from) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result> findAllByPerformedAtBefore(Instant to) { + return Result.failure(STUB_ERROR); + } + + @Override + public Result save(StockMovement stockMovement) { + return Result.failure(STUB_ERROR); + } +} diff --git a/backend/src/main/java/de/effigenix/infrastructure/stub/StubUnitOfWork.java b/backend/src/main/java/de/effigenix/infrastructure/stub/StubUnitOfWork.java new file mode 100644 index 0000000..a04e660 --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/stub/StubUnitOfWork.java @@ -0,0 +1,18 @@ +package de.effigenix.infrastructure.stub; + +import de.effigenix.shared.common.Result; +import de.effigenix.shared.persistence.UnitOfWork; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import java.util.function.Supplier; + +@Component +@Profile("no-db") +public class StubUnitOfWork implements UnitOfWork { + + @Override + public Result executeAtomically(Supplier> work) { + return work.get(); + } +} diff --git a/scripts/generate-openapi.sh b/scripts/generate-openapi.sh index dbe22ba..cf2d8aa 100755 --- a/scripts/generate-openapi.sh +++ b/scripts/generate-openapi.sh @@ -34,7 +34,7 @@ if curl -sf "$API_DOCS_URL" -o /dev/null 2>/dev/null; then echo "✓ Backend läuft bereits" else echo "→ Backend wird gestartet..." - mvn -f "$BACKEND_DIR/pom.xml" spring-boot:run -Pno-db -q & + mvn -f "$BACKEND_DIR/pom.xml" spring-boot:run -Dspring-boot.run.profiles=no-db -q & BACKEND_PID=$! wait_for_backend fi