Kiedy słyszymy „punkty karne", przed oczami staje nam czerwone światełko z policyjnego radiowozu i nieprzyjemne powiadomienie w aplikacji mObywatel. Jednak pod tą prostą, użytkową powłoką kryje się jeden z najciekawszych problemów inżynierii oprogramowania - system, który musi działać z dokładnością transakcyjną banku centralnego, a jednocześnie być dostępny dla dziesiątek milionów kierowców i tysięcy funkcjonariuszy w trybie rzeczywistym. Zbudowanie odpornego na awarie, skalowalnego systemu obsługi punktów karnych wymaga architektury, która łączy przetwarzanie zdarzeń w czasie rzeczywistym, silną spójność danych i rygorystyczne zasady bezpieczeństwa. W tym artykule przeanalizuję, jak od strony technicznej zaprojektować nowoczesną platformę do zarządzania punktami karnymi - od modelu domenowego po Disaster Recovery, czerpiąc z doświadczeń produkcyjnych przy podobnych systemach krytycznych.

Serwerownia z rzędami szaf rack - infrastruktura systemu punkty karne

Domena punktów karnych jako wyzwanie inżynierii oprogramowania

System punkty karne to nie jest zwykła aplikacja CRUD. Jego domena wypełniona jest złożonymi regułami biznesowymi: każdy mandat może dodawać różną liczbę punktów w zależności od daty zdarzenia, upływu okresów przedawnienia, uczestnictwa w szkoleniach redukujących punkty oraz współistnienia równoległych postępowań sądowych. W produkcji zetknęliśmy się z sytuacją, gdzie pojedyncze zdarzenie drogowe generowało od jednego do piętnastu punktów, zależnie od kwalifikacji prawnej, a ta mogła zmienić się nawet po sześciu miesiącach na skutek odwołania. To wymusza zupełnie inne podejście niż proste zwiększanie licznika.

Projektując architekturę, musimy przyjąć, że stan punktów karnych nie jest zmienną, lecz projekcją historii zdarzeń. Tylko w ten sposób można odtworzyć spójną ścieżkę audytu i spełnić wymogi formalne. W praktyce oznacza to przejście na model event sourcingu z przyrostową materializacją widoku - idealnie nadający się do implementacji z użyciem Apache Kafka i stream procesorów takich jak Kafka Streams lub Apache Flink. Taka architektura staje się kręgosłupem całego systemu i będzie przewijać się w kolejnych sekcjach.

Projektowanie modelu danych odpornego na błędy

Centralną encją domeny są oczywiście punkty karne, ale kluczowe jest oddzielenie zdarzenia (np. „Nałożono 6 punktów za przekroczenie prędkości") od kumulatywnego salda. W naszych wdrożeniach stosujemy kolekcję PenaltyEvent z polami: eventId (UUID v7), driverId, points, reasonCode, effectiveDate, expiryDate, oraz metadanymi śledzącymi źródło (np identyfikator mandatu, urządzenie rejestrujące, dane funkcjonariusza). Saldo jest widokiem materializowanym w Redis z TTL synchronizowanym z systemem rekalkulacji.

Dlaczego Redis? Ponieważ warunek brzegowy mówi o czasie odpowiedzi API poniżej 50 ms dla 99. percentyla przy 50 tysiącach zapytań na sekundę - a to realne obciążenie w szczycie komunikacyjnym, gdy setki patroli jednocześnie sprawdzają stan kierowców podczas długiego weekendu. Wewnętrzne benchmarki pokazały, że PostgreSQL 15 nawet z partycjonowaniem i indeksami częściowymi nie gwarantował takich czasów bez dodatkowej warstwy cache'ującej. Oczywiście Redis pełni tylko rolę odczytową; źródłem prawdy pozostaje dziennik zdarzeń w Kafka. Więcej o optymalizacji baz danych w systemach zdarzeniowych znajdziesz w naszym przewodniku.

Architektura sterowana zdarzeniami z Apache Kafka

Sercem systemu punkty karne jest klaster Apache Kafka. Każdy mandat, decyzja sądu czy ukończenie szkolenia trafia do odpowiedniego topiku jako niezmienne zdarzenie - zgodnie ze specyfikacją Kafka Exactly-Once Semantics. Dzięki temu eliminujemy ryzyko podwójnego naliczenia punktów, co przy manualnych procesach synchronizacji plików CSV z policyjnych baz zdarzało się nagminnie. W jednym z projektów wdrożenie transakcyjnego producenta Kafka oraz izolacji read-committed dla konsumentów pozwoliło zejść z poziomu 1,2% rozbieżności sald do zera w ciągu 72 godzin od migracji.

Zdarzenia są konsumowane przez mikroserwis PointsAggregator, który utrzymuje widok salda w Redis i jednocześnie emituje zdarzenia domenowe „PointsBalanceChanged", wykorzystywane przez inne moduły - np do wysyłki powiadomień push o przekroczeniu progu 24 punktów. Całość spięta jest Confluent Schema Registry, które wymusza ewolucję schematów zgodną w przód i wstecz (Avro), minimalizując awarie przy rolling update'ach w środowisku Kubernetes. Taka architektura umożliwiła nam dzienne przetwarzanie 15 milionów zdarzeń na klastrze 6 brokerów bez degradacji opóźnień.

Fragment kodu konfiguracji Kafka Streams dla systemu punkty karne

Gwarancje transakcyjne i integralność punktów karnych

W systemie punkty karne spójność danych jest krytyczna: kierowca nie może zobaczyć salda, które nie uwzględnia już zakończonego szkolenia redukującego punkty, zaś nieprawidłowe wyzerowanie licznika mogłoby narazić instytucję na odpowiedzialność prawną. Dlatego zastosowaliśmy wzorzec Outbox Pattern w warstwie zapisu do PostgreSQL oraz transakcyjne wysyłanie do Kafki za pomocą Debezium i zmiany capture (CDC). Dzięki temu nawet w przypadku nagłego kill-a POD-a przed potwierdzeniem offsetu, zdarzenie zostanie albo w pełni opublikowane, albo wcale - nigdy w stanie pośrednim.

Współbieżność rozwiązujemy przez optymistyczne blokowanie wersji w Redis (porównywanie revision z wartością z nagłówka If-Match). Przy konfliktach - np równoczesnym przetwarzaniu mandatu i decyzji sądu dla tego samego kierowcy - włączamy mechanizm ponownego odczytu strumienia zdarzeń z Kafki i rekalkulacji salda od ostatniego snapshotu. Ta metoda, opisana w dokumentacji RFC 7519 (JWT) w kontekście idempotentnych tokenów API, sprawdza się doskonale także tutaj: każde zdarzenie opatrzone jest unikalnym idempotencyKey, a warstwa PointsAggregator stosuje deduplikację na podstawie tego klucza.

Skalowalne API i strategia zarządzania ruchem

Ruch do API punkty karne jest silnie sezonowy - gwałtowne wzrosty podczas akcji „Prędkość" czy świąt. Zaprojektowaliśmy zestaw endpointów GraphQL (z nakładką REST dla kompatybilności z terminalami mobilnymi patroli) stojących za bramką Kong API Gateway. Kong zapewnia rate limiting na poziomie klienta (klucz API per jednostka organizacyjna) oraz autoryzację przez OAuth2 z tokenami JWT zawierającymi tzw. scope dost

.

Need a Custom App Built?

Let's discuss your project and bring your ideas to life.

Contact Me Today →

Back to Online Trends