O transakcjach być może słyszałaś już w kontekście baz danych — jest to jeden z najbardziej rozpowszechnionych przykładów. Postaramy się odpowiedzieć czym są transakcje, jak wpływają na codzienną pracę z kodem oraz jak korzystać z nich bezpiecznie.
#TeoriaIT to wpisy, które rozszerzą Twoja wiedzę o teorię, przydatna znacznie częściej na rozmowach kwalifikacyjnych niż w codziennej pracy. Mamy nadzieję, że przygotowane przez nas posty uznasz za ciekawe i dzięki nim Świat IT będzie miał dla Ciebie mniej tajemnic.
Czym są transakcje
Transakcja to w ogólnym ujęciu zbiór operacji, które zmieniają stan systemu (np. dane przechowywane w tabelach w przypadku baz danych lub parametry aplikacji) w kilku krokach, i tylko wykonanie wszystkich tych kroków może być uznane za sukces. Jeśli któryś z kroków się nie powiedzie, należy wycofać wszystkie zmiany dokonane w ramach transakcji.
Najprostszą analogią do rzeczywistości są zwykłe zakupy w sklepie z obsługą — pierwszym krokiem jest poproszenie obsługi o produkt, drugim wydanie tego produktu, trzecim wygenerowanie paragonu i czwartym zapłata. Niepowodzenie któregokolwiek z nich (np. zapomnieliśmy portfela i nie możemy zapłacić) powoduje konieczność anulowania wszystkich poprzednich czynności.
Zakończenie transakcji powodzeniem prowadzi do tzw. commita — zapisania wszystkich zmian w ‚głównym’ stanie i udostępnienie ich innym w przyszłości. Niepowodzenie transakcji wymaga tzw. rollbacka — przywrócenia stanu sprzed transakcji, czyli anulowania wszystkich wprowadzonych do tej pory zmian.
Systemy transakcyjne
Choć najprawdopodobniej z transakcjami spotkasz się głównie w kontekście baz danych, istnieje także bardziej abstrakcyjne pojęcie systemów transakcyjnych. Są to aplikacje, które przechowują wewnętrzny stan i zarządzają nim w sposób transakcyjny — izolując poszczególne zmiany od siebie. Systemy takie mogą być rozproszone, mogą też składać się z wielu aplikacji i podsystemów. Weźmy np. system bankowy do obsługi przelewów. System taki, realizując przelew, musi obniżyć saldo osobie, która wysyła przelew, podnieść saldo osobie otrzymującej oraz w razie potrzeby zweryfikować reguły bezpieczeństwa, zapisać informacje na liście transakcji, powiadomić odpowiednie systemy banku centralnego. Wszystko to musi się odbywać w ramach jednej transakcji — niepowodzenie któregokolwiek kroku musi skutkować wycofaniem wszystkich. Praca z dużymi systemami transakcyjnymi jest jedną z bardziej wymagających i jednocześnie ciekawszych wyzwań, jakie mogą stać przed programistą :) Pod koniec znajdziesz przykłady narzędzi, które są pomocne w realizowaniu tego typu aplikacji.
ACID
ACID to akronim od angielskich słów atomicity — consistency — isolation — durability. Są to cechy oczekiwane od każdego systemu transakcyjnego, w szczególności od baz danych. Pozwalają one czynić pewne założenia na etapie projektowania i implementacji aplikacji i skupiać się na logice biznesowej, a nie zarządzaniem środowiskiem, w którym może istnieć wiele transakcji.
Atomicity — (pol. atomowość) niepodzielność transakcji, transakcja może albo zakończyć się w całości powodzeniem albo nie. Dzięki temu mamy gwarancje, że albo wykonane zostaną wszystkie operacje, albo żadna z nich.
Consistency — (pol. spójność) utrzymanie spójności w całym kontekście, jakiego dotyczy transakcja. Innymi słowy wszystkie operacje wykonywane w ramach transakcji muszą prowadzić do spójnego stanu, tzn. takiego, który można scalić z innymi zmianami i który jest zgodny z zasadami systemu.
Isolation — (pol. izolacja) rozdzielenie pomiędzy transakcjami — zmiany i operacje wykonywane w jednej transakcji domyślnie nie mogą wpływać na dane i stan przetwarzany w drugiej transakcji.
Durability — (pol. trwałość) o ile transakcja zakończy się sukcesem, zmiana stanu, która w niej zaszła, powinna być przechowywana i stała.
JTA, Spring
W Javie istnieje standardowy sposób zarządzania transakcjami, także rozproszonymi, pod nazwą JTA — Java Transaction API. Podobnie jak JPA, definiuje on API, które może być implementowane przez dowolną bibliotekę, której zdecydujemy się użyć. Problemem do tej pory był fakt, że transakcje te były respektowane tylko w ramach EJB (uległo to zmianie w Javie EE 7). Z tego powodu Spring posiada własny system zarządzania transakcjami, który jednak jest zgodny (i łatwo integrowalny) z JTA.
Jak się zapewne domyślasz, w tym wypadku sama musisz zaimplementować logikę do ‘odwracania’ transakcji — jest to zależne od systemu i czasem niemożliwe w 100% (np. jeśli już został wysłany mail lub powiadomiony zewnętrzny system — nie da się takiej czynności ‘cofnąć’, trzeba zaradzić jej skutkom — wysłać maila z przeprosinami, anulować transakcje w innym systemie itp). Polecamy szczególnie opis z dokumentacji Springa — omawia on cały koncept zarządzania transakcjami w Springu i stosowne interfejsu oraz adnotacje (z których główna to @Transactional). Transakcje w Springu są implementowane z wykorzystaniem AOP.
Tutorial Spring o korzystaniu z transakcji w połączeniu z JTA
JPA i bazy danych
Większość współczesnych baz danych domyślnie działa w oparciu o transakcje, nawet bez naszej wiedzy — domyślny tryb to otwieranie transakcji przed każdym zapytaniem SQL i jej commitowanie po wykonaniu danego zapytania. Implementacje JPA korzystają z tych mechanizmów integrując je jednocześnie z opisanym wcześniej JTA — dzięki temu aplikacje Javy mogą łączyć operacje na bazie danych z innymi działaniami w ramach jednej transakcji. Z transakcji korzystamy np. używając Springowej adnotacji @Transactional, lub — jeśli korzystamy bezpośrednio z Hibernate — korzystając z obiektów implementujących org.hibernate.Session.
Transakcje możemy także obsługiwać z poziomu języka SQL, więcej znajdziesz w tutorialu tutorialspoint oraz w dokumentacji MySQL.
Transakcje rozproszone
W tym przypadku mamy dwie możliwości — samodzielnej implementacji jednego z algorytmów lub skorzystania z gotowych rozwiązań. Gorąco polecamy tą drugą opcję, jednak mimo tego warto zapoznać się z podstawami algorytmów, które wykorzystują.
Two-phase commit
Jest to jeden z pierwszych i prostszych protokołów pozwalających zarządzać zdalnymi transakcjami. Lider wysyła prośbę do wszystkich zewnętrznych systemów o dokonanie pewnej zmiany — może to być np. wykonanie określonej operacji, ale bez commitowania jej. Następnie zewnętrzne systemy (w algorytmie tym nazywane kohortą) odsyłają odpowiedź z informacją czy udało się wykonać operację czy nie. Lider na postawie tych informacji podejmuje decyzje, czy całość ma zostać zaakceptowana czy odrzucona, po czym wysyła stosowną informację do kohorty.
Protokół Paxos
Jest to tak naprawdę grupa kilku protokołów do osiągania konsensusu w rozproszonych systemach, które mogą być wykorzystywane także do zarządzania zdalną transakcją. Ich głównym wyróżnikiem jest możliwość balansowania pomiędzy różnymi cechami — szybkością, pewnością osiągnięcia konsensusu, odpornością na błędy i problemy w komunikacji itp. Dzięki temu mają szerokie zastosowanie w różnych systemach — przykładowo implementacja o nazwie Apache Mesos jest wykorzystywana w tak różnych systemach jak eBay, Airbnb czy Twitter.
Apache Zookeeper
Apache Zookeeper to narzędzie do koordynowania rozproszonych systemów — opiera się ono na rozproszonych lockach i koordynowaniu dostępu do zasobów, także może zostać wykorzystane do zarządzania transakcjami rozproszonymi.
Podsumowanie
Transakcje, to operacje, które można podsumować: wykona się wszystko,albo nic. Takie podejście może okazać się korzystne dla wielu rozwiązań, w szczególności, gdy operacje, które obsługujemy są wieloetapowe. Oczywiście warto mieć świadomość, że nie każde działanie zawarte w transakcji można cofnąć (jak wspomnianą już przez nas wysyłkę e‑mail), dlatego projektując transakcje trzeba dokładnie znać jej przebieg.