Krzysztof Olszewski
Dyrektor Technologii i Architektury Oprogramowania
Krzysztof Olszewski
Dyrektor Technologii i Architektury Oprogramowania
Klasyczne środowisko informatyczne jest silnie przywiązane do transakcji. Transakcja jest piękna. Mówiąc górnolotnie, transakcja przenosi system z jednego stabilnego stanu w drugi także stabilny. Cokolwiek by się nie udało w trakcie jej trwania, daje nam gwarancję, że wszystkie zmiany się wycofają i system pozostaje w stanie spójnym. Stosowanie transakcji jest ogromnie wygodne dla programistów, głównie poprzez to, że łatwo się myśli i pisze kod. Jak łatwo się domyślić, skutkiem powyższego jest nadużywanie transakcji. Nagminnie robi się je za długie. Za długie w dwóch głównych wymiarach
- W wymiarze ilości iteracji, w którym zamiast schematu „jeden item jedna krótka transakcja”, robi się schemat „wiele itemów jedna długa transakcja” np. import 1000 pozycji paragonu w jednej długiej transakcji.
- A także w wymiarze łączenia różnych kontekstów w jedną długą transakcję np. dodanie paragonu, jego zaksięgowanie i przesłanie do rozrachunków w jednej złożonej transakcji.
O ile w pierwszym przypadku rozwiązanie problemu, lub jego nie powodowanie jest dosyć proste, o tyle w drugim przypadku jest znacznie trudniej. Rozważmy taką dużą transakcję w klasycznym systemie
Transakcja jak widać jest długa, składa się z trzech etapów, co gorsze każdy z tych etapów działa w innym kontekście merytorycznym (sprzedaż, księgowość, rozliczenia). Jednak wiemy, że gdyby którykolwiek z tych etapów się nie udał, ma się nie udać (wycofać) całość. Gdyby tak się nie stało system przechodzi w stan nieustalony, a do tego nie chcemy dopuścić. Jak zatem skrócić tą transakcję?
Pierwsza metoda znana jest pod nazwą znacznikowania. Polega ona na tym, że dodajemy kolumnę w dokumencie która oznacza stan jego przetwarzania, po czym cały kod przenosimy do kontekstu nietransakcyjnego, a każdą z operacji umieszczamy w transakcji z jednoczesną zmianą statusu dokumentu, mniej więcej tak
Mamy trzy osobne, małe transakcje, każda działa w osobnym kontekście. Co ważne, po każdej system pozostaje w stanie stabilnym. Gdyby nie udało się dodać dokumentu do rozliczeń, nic złego się nie dzieje, dokument to „wie” bo ma znacznik statusu równy „Zaksięgowany” a nie „Gotowy”. Dalsze procesy biznesowe będą także „wiedzieć”, że taki dokument nie może być dalej przetwarzany (chyba, że uznają, że może). Powtórne uruchomienie całego procesu pominie dwa pierwsze etapy (patrząc na status) i spróbuje wykonać ostatnią transakcję. Proste i skuteczne. Ale! Co prawda nie mamy już dużej transakcji, ale mamy kod, nazwijmy go sterującym, który steruje transakcjami z bardzo różnych kontekstów. Z różnych względów możemy mieć pokusę aby pozbyć się także tej niezręczności.
Jedną z metod na pełne rozdzielenie techniczne i kontekstowe transakcji, jest wprowadzenie architektury zdarzeniowej. Każda „mała” transakcja oprócz swojej pracy rozgłasza jeszcze informację, że tą pracę wykonała. Każdy kontekst nasłuchuje interesujących go komunikatów i jak je otrzyma wykonuję swoją transakcję. Modelowo wygląda to tak
Transakcja może być wywołana albo komendą z zewnątrz albo faktem odebrania jakiegoś zdarzenia. W ramach transakcji jest wykonanie działania oraz przesłanie informacji, że ono się wykonało. Kto wywołał komendę? Kto przysłał zdarzenie? Kto odbierze nasze zdarzenie? Nie wiadomo. I to bardzo dobrze. Nie ma kodu (procesu), który by w niepokojący sposób łączył kompetencje z różnych, niezależnych kontekstów. Jak zatem wyglądał by nasz proces przetworzony do tego schematu, mniej więcej tak
Mamy i krótkie transakcje i też nie łączymy w żadnym bycie kodu z różnych kontekstów. Czyli bajka :). Jednak nie ma róży bez kosztów i za tą bajkę przychodzi nam słono zapłacić. Wyobraźcie sobie system gdzie wszystko „lata” po zdarzeniach, nie ma już kodu który linia po linii realizuje jakąś logikę sterującą. Jak to śledzić? Jak zapanować nad strumieniami różnych komunikatów? Co z ich zależnościami czasowymi? Co ze złożonością która tak niefartownie wzrosła? Każdy musi odpowiedzieć sam na te pytania, a także na to najważniejsze. Czy w naszym przypadku warto ponieść koszt takiej architektury, czy korzyści przewyższą koszty?
Ja od siebie dodam, że kiedyś dawno temu brałem udział w tworzeniu systemu opartego na zdarzeniach. Pisałem wtedy tak
create trigger biu_books after update on books
as
begin
if (new.price <> old.price)then begin
// … do something in my module
end
end
Czy mi się to sprawdziło? Do pewnych zastosowań tak. Ale nadużywanie tego mechanizmu wpędziło system i twórców w niemałe kłopoty. Podsumowując, skracanie transakcji w każdym wymiarze, (do rozmiarów uzasadnionych) jest bardzo potrzebne. Metod jest wiele i należy zawsze dobierać odpowiednią metodę do wymagań i potrzeb.