Aplikacje serwerowe zwykle wykorzystują jedną główną bazę danych. Gdy aplikacja lub mikroserwis operuje wyłącznie na niej, utrzymanie transakcyjności jest stosunkowo proste. Problem pojawia się w momencie, gdy aplikacja musi dodatkowo wysłać wiadomość na kolejkę lub zapisać dane w innym mikroserwisie. W takiej sytuacji zewnętrzna usługa znajduje się poza kontrolą lokalnej transakcji.
Jest to istotny problem w systemach rozproszonych, dlatego wypracowano dobre praktyki pozwalające zachować integralność danych.
Przykładowy scenariusz:
chcemy zapisać dane w tabeli oraz wysłać informację na kolejkę o poprawnym wykonaniu operacji. Jest to bardzo częsty przypadek biznesowy. Zamiast kolejki może występować dowolny inny system zewnętrzny — mechanizm działania pozostaje taki sam.
Jak rozwiązać ten problem, aby uniknąć niespójności danych? Jednym z najpopularniejszych podejść jest wzorzec Transactional Outbox.
Dzięki temu rozwiązaniu zyskujemy pewność, że jeśli zapis danych zakończy się błędem, wiadomość nie zostanie wysłana na kolejkę. Pozwala to uniknąć niespójności danych oraz ograniczyć konieczność późniejszej obsługi błędów. W praktyce przekłada się to na oszczędność czasu oraz zasobów — zarówno finansowych, jak i ludzkich.
Dużym problemem w aplikacjach webowych jest zarządzanie równoczesnymi zmianami danych w bazie. Jeżeli wielu użytkowników zapisuje niezależne dane w tej samej tabeli, zwykle nie stanowi to problemu. Należy jednak przewidywać sytuacje konfliktowe.
Przykładowy przypadek:
Mamy rekord w tabeli meetings. Dwóch użytkowników jednocześnie odczytuje te same dane. Następnie obaj próbują zmienić nazwę tego samego wpisu. Co się stanie? Najczęściej utrwalona zostanie ostatnia zapisana zmiana, a wcześniejsza zostanie nadpisana.
Aby temu zapobiec, stosuje się mechanizmy blokowania danych.
Podejście optymistyczne zakłada, że konflikt wystąpi rzadko. Dane nie są blokowane podczas odczytu, a konflikt wykrywany jest dopiero podczas zapisu.
W Spring najczęściej realizuje się to poprzez dodanie pola oznaczonego adnotacją @Version w encji. Podczas zapisu framework sprawdza numer wersji rekordu. Jeśli w międzyczasie rekord został zmodyfikowany przez innego użytkownika, rzucany jest wyjątek informujący o konflikcie.
Przykład:
@Version
private Long version;
Podejście pesymistyczne zakłada możliwość wystąpienia konfliktu już na początku operacji. Rekord zostaje zablokowany podczas odczytu, dzięki czemu inny użytkownik nie może go jednocześnie modyfikować.
Przykład w Spring:
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query(„select a from Data a where a.id = :id”)
Optional findByIdForUpdate(@Param(„id”) Long id);
W tym przypadku kolejny użytkownik będzie musiał poczekać, aż blokada zostanie zwolniona. Rozwiązanie to zwiększa bezpieczeństwo danych, ale może obniżyć wydajność systemu przy dużej liczbie równoczesnych operacji.
Skontaktuj się z nami: Kontakt