Nie korzystaj z Axona i Tracking Processor

Lubię placki i Axona. Przez większość czasu działa szybko, stabilnie i niezawodnie, a problemy, które sprawia jakoś da się rozwiązać. Jednak tym razem będę namawiał na całkowite porzucenie Tracking Processor. Jest to najlepsza funkcjonalność Axona dodana w wersji 3, a zarazem bardzo niedopracowana. Jeżeli zależy Ci na prędkości przetwarzania i wielowątkowości to musisz z niej zrezygnować. Tracking Processor w Axonie to funkcjonalność śledząca przetwarzanie zdarzeń przez metody, które je obsługują. Daje to łatwe odtworzenie lub dodanie query ponieważ wystarczy usunąć z bazy danych odpowiednie wiersze dla metody obsługującej zdarzenie, procesor to wykryje i zaaplikuje je ponownie. Jednak nie działa to tak sprawnie jak byśmy chcieli.

Problem prędkości przetwarzania

Tracking Processor przetwarza kilka zdarzeń na sekundę.

Przyczyna

Nie dowiedzieliśmy się dlaczego tak się dzieje, a przyznanie dodatkowego wątku do Tracking Processor jeszcze zmniejszyło ilość przetworzonych zdarzeń na sekundę.

Rozwiązanie

Nie korzystać z Tracking Procesora. Po prostu usunąć całą konfigurację z nim związaną. My tak zrobiliśmy i dodaliśmy konfigurację Asynchronous Command Bus, co spowodowało wzrost przetwarzania rozkazów i zdarzeń z 40 do 400 na sekundę.

Dodatek 1

W roku 2018 byłem na Devoxx Poland i z ciekawości poszedłem na prelekcję o Axonie. Tam usłyszałem, że połączenie Axona i relacyjnych baz danych nie działa najlepiej. Nie było dokładnie wyjaśnione „co i dlaczego” ale koleś wydawał się ogarnięty. No spoko, hype development, wywalamy Postgresa i korzystamy z MongoDB… No jednak nie. W dokumentacji Axona możemy przeczytać, że Tracking Processor nie działa efektywnie z MongoDB, smuteczek.

Dodatek 2

Axon wydał nową wersję 4.0. Wymyślił coś takiego jak Axon Server (nie jestem fanem tego rozwiązania) i z tego co widziałem, wykorzystywana jest w nim funkcjonalność Tracking Processor. Mam tylko cichą nadzieję, że chociaż trochę poprawili tą funkcjonalność, przynajmniej z nowej dokumentacji zniknęła wzmianka o nieefektywności.

Wersja Axona: 3.3

Nie korzystaj z Axona i list

To nie jest tak, że w Axonie w agregatach i sagach nie można korzystać z list. Jeżeli kolekcja zawiera niewielką ilość elementów, około 1000, to nie ma problemu. Jeżeli jednak wymagana jest obszerniejsza lista to napotykamy problemy pamięciowe i wydajnościowe. Co należy zrobić aby nie mieć takich problemów?

Problem pamięci masowej

Przechowywanie dużych kolekcji w sadze powoduje duże zużycie pamięci masowej.

Przyczyna

Podczas zapisywania stanu sagi po przetworzeniu zdarzenia, przy wykorzystaniu JPA, stan jest zapisywany w nowym obiekcie w Postgresie. Powoduje to namnożenie obiektów przechowujących stan sagi.

Jeżeli  w sadze mamy listę 1000 UUIDów zajmujących po 128 b i przetworzy ona 1000 zdarzeń, przez co będzie zapisana 1000 razy, to baza zwiększy swój rozmiar o 128 mb.

Rozwiązanie

Konfiguracja JdbcAutoConfiguration pomaga. Po jej zastosowaniu  stan sagi nie będzie zapisywany jako obiekt a kolumna w bazie danych, przez co nowy stan sagi nadpisze stary. Najlepiej po prostu unikać dużych list w sagach. Jeżeli musimy zapisać dużą kolekcję wtedy trzeba stworzyć na nią dodatkową tabelę w bazie danych lub wykorzystać brokera wiadomości. My skorzystaliśmy z drugiej propozycji. Podczas przetwarzania jednego ze zdarzeń na RabbitMQ wysyłamy rozkazy, które następnie zostają ściągnięte i wrzucone na CommandBus.

Problem prędkości przetwarzania

Agregaty czy sagi przechowujące duże listy długo się wczytują i zapisują.

Przyczyna

Axon serializuje i deserializuje długą listę na xmla lub jsona. 

Rozwiązanie

Usuwamy listę. Dla agregatu rozwiązanie jest proste, elementy z listy stają się agregatami, nie ma tu innego rozwiązania. Z sagami może być większy problem lecz najczęściej ta duża lista nie będzie nam potrzebna lub możemy wykorzystać pomysły z wcześniejszego rozwiązania. W jednym z naszych przypadków listę udało zastąpić się zliczaniem opublikowanych zdarzeń.

Wersja Axon: 3.3.0

Nie korzystaj z Axon i JPA

Problemy z JPA i Axonem nie zaczynają się od razu. Początki są wolne, chcesz aby wykonał się jeden rozkaz, później sto rozkazów, wszystko przechodzi swobodnie i szybko, nie widać żadnego problemu. Gdy zaczyna się robić poważnie i musisz wykonać np. czterdzieści tysięcy rozkazów to wtedy zauważasz dziwne zachowanie swojego komputera. Chrom płacze z głodu, Intellij przestaje funkcjonować i nagle dostajesz „Out of Memory exception”. Siedzisz zdziwiony przed komputerem i się zastanawiasz jak prosta logika jednego ifa jest w stanie zjeść szesnaście giga bajtów pamięci RAM w kilka sekund.

Problem pamięci RAM

Podczas przetwarzania dużej ilości rozkazów dla Event Sourcing-owych agregatów występuje duże zużycie pamięci RAM.

Przyczyna

Po profilowaniu aplikacji, można zauważyć duże zużycie pamięci RAM dla klasy TreeSet, która jest wykorzystywana w klasach JPA.

Rozwiązanie

Rozwiązanie jest dojść proste, wystarczy wykorzystać konfigurację
JdbcAutoConfiguration do zapisywania struktur Axonowych w Postgresie. Po zastosowaniu konfiguracji zużycie pamięci spada poniżej dwustu pięćdziesięciu sześciu mega bajtów pamięci RAM.

Wersja Axona: 3.0

Wprowadzenie

Od czterech lat jestem profesjonalnym klepaczem kodu. Od dwóch lat klepię ten kod w dość nowych technologiach takich jak:

  • Spring Boot
  • Spring Boot Data
  • Spring Cloud
  • Axon Framework
  • Postgres
  • RabbitMQ

Przekłada się to na problemy, na które nawet szesnasta zakładka z Stack Overflow nie pomaga. Źródłem tych problemów nie był Spring, spokojnie możesz korzystać dalej, lecz Axon. Event Sourcing i CQRS to nie są proste sprawy, a jak do tego dorzucisz wydajność to można się całkiem załamać. Dlatego przed rozpoczęciem pracy z Axonem chciałbym mieć tę wiedzę, którą teraz posiadam i w tych publikacjach przedstawię wam dlaczego „Nie powinieneś korzystać z Axona”.

Porzucenie

Porzucam projekt tworzenia własnego frameworka.

Usprawnienia

Po częściowej implementacji przyszedł czas na usprawnienie. Pierwsze na czym się skupiłem to agregaty. Nic nowego nie wymyśliłem, po prostu wykonałem metodę Kopiego Pejsta z Axona. Największą zaletą przepisywania tego było zrozumienie jak działa metoda applyAggregateLifecycle i jest to jedna z ciekawszych implementacji jaką widziałem w swoim życiu. Drugim usprawnieniem jest tworzenie serwisów przez metodę wytwórczą. Okazało się, że jest to dojść proste do napisania i nie napotkałem żadnych trudności. Jedyne co mi się nie podoba, to to że podczas wiązania typu z daną metodą trzeba podać typy parametrów wejściowych.

Kod do posta dostępny na GitHubie: Dynamics – Improvements

Na repo nie było nic ciekawego, więc wraz z porzuceniem projektu usunąłem je.

Szybki sprawdzian

Już jakiś czas piszę framework Dynamics. Ilość funkcjonalności się zwiększa, więc trzeba sprawdzić czy to co zostało zaimplementowane do czegoś się przyda. Postanowiłem obsłużyć poruszanie się Pacmana. Jest to jedna z podstawowych funkcji gry dlatego zdecydowałem się zaimplementować ją jako pierwszą.

Potrzebne aby postać mogła się poruszać:

  • plansza (Gameboard)
    • będzie pilnować kolizji ze ścianami i innymi obiektami
  • postać (Pacmana)
    • porusza się po planszy – cyklicznie wysyła rozkazy ruchu na kolejne pole
    • zmienia kierunek ruchu
  • konfiguracja serwisów:
    • obsługa rozkazów
    • obsługa zdarzeń
    • obsługa rozkazów planszy
    • obsługa rozkazów postaci

W tym momencie nie muszę się skupiać nad jakością kodu ponieważ jest to tylko testowa implementacja.

Jak to wygląda.

Na filmie w głównym oknie możemy zaobserwować poruszającą się postać, a poniżej w konsoli wysyłane rozkazy i publikowane zdarzenia.

Kod do posta dostępny na GitHubie: Dynamics – Quick test

Na repo nie było nic ciekawego, więc wraz z porzuceniem projektu usunąłem je.

Lubię DDD

Na studiach nauczono mnie programować obiektowo, lecz w pracy ta umiejętność nie bardzo mi się przydała. Pierwszy duży projekt, z którym się w niej spotkałem, był napisany w architekturze trójwarstwowej. Musiałem zapomnieć o tym czego się wcześniej nauczyłem i zacząć pisać strukturalny kod jak w C tylko, że w Javie. Nie widziałem sensu pisania kodu w taki sposób. Miesiącami próbowałem zrozumieć dlaczego tak, a nie inaczej napisana jest aplikacja. Podczas analizy w mojej głowie pokazywały się rozwiązania obiektowe, które były łatwiejsze w zrozumieniu i krótsze niż serwisy opisane w 1000 linii, w których znajduje się wszystko.

Jestem po kilkudziesięciu wykładach i książce z „Czystego Kodu” („Czysty kod. Podręcznik dobrego programisty” – Robert C. Martin). Nie było to dla mnie nic odkrywczego, pomogło mi jednak w nazwaniu niektórych rzeczy, co ułatwia współpracę w zespole. Same zasady czystego kodu nie pokazują jak pisać kod obiektowo. Tu z odsieczą przyszli Vaughn Vernon i Eric Evans („Implementing Domain Drive Design”, „Domain-Driven Design: Tackling Complexity in the Heart of Software”), którzy pokazali jak to robić oraz jak to robić z biznesem.

Zgodnie z zasadą czystego kodu wszystko co zostanie napisane musi zostać przetestowane. W Unity3D stosuje się obiekty MonoBehaviour, w których znajduje się większość logiki. Przez długi okres czasu obiekty te były „nietestowalne”. Twórcy Unity chcąc pomóc programistom zaczęli tworzyć tutoriale jak rozdzielać obiekty tak aby choć po części dało się je przetestować.

Moim zdaniem nie wygląda to za dobrze. Jak dla mnie MonoBehaviour powinny zajmować się tylko wyświetlaniem  modelu albo zbieraniem danych od użytkownika. Cała reszta logiki gry powinna znajdować się gdzieś pod spodem. Ostatnio znalazłem przydatne narzędzie:

które umożliwia testy obiektów MonoBehaviour, z czego bardzo się ucieszyłem.

DDD skłoniło mnie do myślenia o tym jakich narzędzi brakuje mi w Unity. Dlatego zacząłem pisać framework „Dynamics”. W pierwszej wersji chciałbym aby projekt zawierał trzy rzeczy:

  • obsługę rozkazów
  • obsługę zdarzeń
  • kontener z zależnościami

Pierwszą poboczną funkcjonalnością w Dynamics, którą będę potrzebował to planista. Będzie on wykorzystywany w CommandBus i EventBus. Dzięki IoC w łatwy sposób będę mógł podmienić jego implementacja z jednowątkowej na wielowątkową. Planista będzie kolejkował zdefiniowane przez inne obiekty akcje. Mógłbym skorzystać z .Net Reactive Extension lecz niestety nie działa w Unity3D. Ktoś już zauważył ten brak i stworzył implementację dla Unity3D. Nazwał ją UniRx. Nie skorzystam jednak z gotowego rozwiązania, aby zwiększyć swoją przyjemność z pisania. Kolejną funkcjonalnością do napisania po planiście będzie obsługa poleceń. Każda zmiana stanu gry rozpocznie się od rozkazu, który nazwą wskazuje na intencję, a we wnętrzu posiada dane do podjęcia akcji. Podczas pisania będę się wzorował na Axon Framework. Następnie napiszę obsługę zdarzeń, które wyrażają zmianę stanu gry.

Kod można znaleźć na GitHubie: Dynamics – I like ddd

Na repo nie było nic ciekawego, więc wraz z porzuceniem projektu usunąłem je.

Pierwszy kontakt

W pracy głupot robić nie mogę (klient się na to nie zgadza),  więc w domu muszę się wyszaleć. Dlatego postanowiłem napisać własny framework do Unity3D. Od kilku lat starałem się napisać jakieś gry ale zawszę kończyło się to fiaskiem. Głownie ze względu na brak testów i ciężki do utrzymania kod. Po kilku miesiącach tworzenia, gdy stworzyłem trochę klas, zaczynały się problemy z wprowadzaniem nowych funkcjonalności. Pierwszą rzeczą, której mi brakowało to Inversion of Control, często wykorzystywane w springu, angularze czy innych frameworkach, a w Unity3D uczy aby wykorzystywać singletony link.

Swoją pracę nad IoC kontenerem dla własnego frameworka rozpocząłem od sprawdzenia już gotowych rozwiązań:

  • Unity
  • Ninject
  • Zenject

Zobaczyłem co miałem zobaczyć i jestem gotowy do rozpoczęcia prac.

Kod można znaleźć na GitHubie: Dynamics – First contact

Na repo nie było nic ciekawego, więc wraz z porzuceniem projektu usunąłem je.

Hello Word

main()
{
    printf(„Hello World”);
}