1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 08:29:36 +01:00

feat: Paginierung für alle GET-List-Endpoints (#61)

Einheitliches Paginierungs-Pattern mit page, size und Multi-Field sort
für alle 14 List-Endpoints. Response-Format ändert sich von [...] zu
{ content: [...], page: { number, size, totalElements, totalPages } }.

Backend:
- Shared Kernel: Page<T>, PageRequest, SortField, SortDirection
- PaginationHelper (SQL ORDER BY mit Whitelist), PageResponse DTO
- Paginated Methoden in allen 14 Domain-Repos + JDBC-Implementierungen
- Safety-Limit (500) für findAllBelowMinimumLevel/ExpiryRelevantBatches
- Alle List-Use-Cases akzeptieren PageRequest, liefern Page<T>
- Alle Controller mit page/size/sort Query-Params + PageResponse

Frontend:
- PagedResponse<T> Type auf nested page-Format aktualisiert
- Alle 14 API-Client-Resourcen liefern PagedResponse mit PaginationParams
- Alle Hooks mit Pagination-State (currentPage, totalPages, pageSize)
- Alle List-Screens mit Seiten-Navigation (Pfeiltasten) und Footer

Loadtest:
- Podman-Support im justfile (DOCKER_HOST auto-detect)
- Verschärfte Performance-Schwellwerte basierend auf Ist-Werten
This commit is contained in:
Sebastian Frick 2026-03-20 16:33:20 +01:00
parent fc4faafd57
commit 72979c9537
151 changed files with 2880 additions and 1120 deletions

View file

@ -100,8 +100,7 @@ public final class LoadTestInfrastructure {
// Podman rootless Socket
Path podmanSocket = Path.of("/run/user/" + getUid() + "/podman/podman.sock");
if (Files.exists(podmanSocket)) {
System.setProperty("DOCKER_HOST", "unix://" + podmanSocket);
System.setProperty("TESTCONTAINERS_RYUK_DISABLED", "true");
configurePodman("unix://" + podmanSocket);
System.out.println("=== Podman erkannt: " + podmanSocket + " ===");
return;
}
@ -109,12 +108,17 @@ public final class LoadTestInfrastructure {
// Podman root Socket
Path podmanRootSocket = Path.of("/run/podman/podman.sock");
if (Files.exists(podmanRootSocket)) {
System.setProperty("DOCKER_HOST", "unix://" + podmanRootSocket);
System.setProperty("TESTCONTAINERS_RYUK_DISABLED", "true");
configurePodman("unix://" + podmanRootSocket);
System.out.println("=== Podman (root) erkannt: " + podmanRootSocket + " ===");
}
}
private static void configurePodman(String dockerHost) {
System.setProperty("DOCKER_HOST", dockerHost);
System.setProperty("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", dockerHost.replace("unix://", ""));
System.setProperty("TESTCONTAINERS_RYUK_DISABLED", "true");
}
private static String getUid() {
try {
var process = new ProcessBuilder("id", "-u").start();

View file

@ -78,62 +78,61 @@ public class FullWorkloadSimulation extends Simulation {
)
).protocols(httpProtocol)
.assertions(
// Global: Login (BCrypt ~230ms) hebt den Schnitt
global().responseTime().percentile(95.0).lt(500),
global().responseTime().percentile(99.0).lt(1000),
global().failedRequests().percent().lt(5.0),
// Global
global().responseTime().percentile(95.0).lt(250),
global().responseTime().percentile(99.0).lt(400),
global().failedRequests().percent().lt(1.0),
// Login darf langsam sein (BCrypt strength 12)
details("Login [admin]").responseTime().mean().lt(350),
details("Login [admin]").responseTime().percentile(95.0).lt(500),
// Login (BCrypt strength 12 ~230ms)
details("Login [admin]").responseTime().mean().lt(280),
details("Login [admin]").responseTime().percentile(95.0).lt(350),
// Einzeldatensatz-Reads: streng (mean < 20ms)
details("Rezept laden").responseTime().mean().lt(20),
details("Artikel laden").responseTime().mean().lt(20),
details("Lieferant laden").responseTime().mean().lt(20),
details("Kunde laden").responseTime().mean().lt(20),
// Einzeldatensatz-Reads: mean < 10ms
details("Rezept laden").responseTime().mean().lt(10),
details("Artikel laden").responseTime().mean().lt(10),
details("Lieferant laden").responseTime().mean().lt(10),
details("Kunde laden").responseTime().mean().lt(10),
details("Inventur laden").responseTime().mean().lt(10),
details("Lagerort laden").responseTime().mean().lt(10),
// Listen mit wenig Daten (< 30 Einträge): mean < 35ms
details("Rezepte auflisten").responseTime().mean().lt(35),
details("Lagerorte auflisten").responseTime().mean().lt(35),
details("Bestände auflisten").responseTime().mean().lt(35),
details("Bestände unter Minimum").responseTime().mean().lt(35),
details("Bestände nach Lagerort").responseTime().mean().lt(35),
details("Lieferanten auflisten").responseTime().mean().lt(35),
details("Kategorien auflisten").responseTime().mean().lt(35),
details("Bestandsbewegungen auflisten").responseTime().mean().lt(35),
details("Bestandsbewegungen nach Bestand").responseTime().mean().lt(35),
details("Bestandsbewegungen nach Charge").responseTime().mean().lt(35),
details("Bestandsbewegungen nach Zeitraum").responseTime().mean().lt(35),
details("Inventuren auflisten").responseTime().mean().lt(35),
details("Inventur laden").responseTime().mean().lt(20),
// Paginated Listen (20er Seiten): mean < 15ms
details("Rezepte auflisten").responseTime().mean().lt(15),
details("Lagerorte auflisten").responseTime().mean().lt(15),
details("Bestände auflisten").responseTime().mean().lt(15),
details("Bestände unter Minimum").responseTime().mean().lt(15),
details("Bestände nach Lagerort").responseTime().mean().lt(15),
details("Lieferanten auflisten").responseTime().mean().lt(15),
details("Kategorien auflisten").responseTime().mean().lt(15),
details("Bestandsbewegungen auflisten").responseTime().mean().lt(15),
details("Bestandsbewegungen nach Bestand").responseTime().mean().lt(15),
details("Bestandsbewegungen nach Charge").responseTime().mean().lt(15),
details("Bestandsbewegungen nach Zeitraum").responseTime().mean().lt(15),
details("Inventuren auflisten").responseTime().mean().lt(15),
details("Produktionsaufträge auflisten").responseTime().mean().lt(15),
details("Produktionsaufträge nach Status").responseTime().mean().lt(15),
// Listen mit viel Daten (50-300 Einträge): mean < 75ms
details("Chargen auflisten").responseTime().mean().lt(75),
details("Artikel auflisten").responseTime().mean().lt(75),
details("Kunden auflisten").responseTime().mean().lt(75),
// Listen mit Children-Loading: mean < 25ms
details("Chargen auflisten").responseTime().mean().lt(25),
details("Artikel auflisten").responseTime().mean().lt(25),
details("Kunden auflisten").responseTime().mean().lt(25),
// Garantiert vorkommende Write-Requests: moderat (mean < 50ms)
details("Charge planen").responseTime().mean().lt(50),
details("Charge starten").responseTime().mean().lt(50),
details("Charge abschließen").responseTime().mean().lt(50),
details("Produktionsauftrag anlegen").responseTime().mean().lt(50),
details("Produktionsauftrag freigeben").responseTime().mean().lt(50),
details("Produktionsauftrag abschließen").responseTime().mean().lt(50),
details("Produktionsauftrag stornieren").responseTime().mean().lt(50),
details("Produktionsauftrag umterminieren").responseTime().mean().lt(50),
details("Bestandsbewegung erfassen").responseTime().mean().lt(50),
details("Inventur anlegen").responseTime().mean().lt(50),
details("Inventur starten").responseTime().mean().lt(50),
details("Ist-Menge erfassen").responseTime().mean().lt(50),
// Write-Requests: mean < 20ms
details("Charge planen").responseTime().mean().lt(20),
details("Charge starten").responseTime().mean().lt(20),
details("Charge abschließen").responseTime().mean().lt(20),
details("Produktionsauftrag anlegen").responseTime().mean().lt(20),
details("Produktionsauftrag freigeben").responseTime().mean().lt(20),
details("Produktionsauftrag abschließen").responseTime().mean().lt(20),
details("Produktionsauftrag stornieren").responseTime().mean().lt(20),
details("Produktionsauftrag umterminieren").responseTime().mean().lt(20),
details("Bestandsbewegung erfassen").responseTime().mean().lt(20),
details("Inventur anlegen").responseTime().mean().lt(20),
details("Inventur starten").responseTime().mean().lt(20),
details("Ist-Menge erfassen").responseTime().mean().lt(20),
// Tracing: BFS-Traversierung, mean < 50ms
details("Charge vorwärts tracen").responseTime().mean().lt(50),
details("Charge rückwärts tracen").responseTime().mean().lt(50),
// Produktionsaufträge-Listen: mean < 35ms
details("Produktionsaufträge auflisten").responseTime().mean().lt(35),
details("Produktionsaufträge nach Status").responseTime().mean().lt(35)
// Tracing (BFS): mean < 15ms
details("Charge vorwärts tracen").responseTime().mean().lt(15),
details("Charge rückwärts tracen").responseTime().mean().lt(15)
);
}
}