Patterns of Enterprise Application Architecture – wrażenia po lekturze

Ten wpis nie będzie kompletną recenzją książki Patterns of Enterprise Application Architecture Martina Fowlera, a jedynie kilkoma zebranymi w jednym miejscu spostrzeżeniami. Książkę uznałem za wartą przeczytania ze względu na to, jak szeroko jest cytowana. Czy zrobiłem słusznie, czy nie, o tym za chwilę.

Książka zaczyna się od wstępu stanowiącego przegląd problemów, które pojawiają się w aplikacjach klasy enterprise. Jako przykłady takich aplikacji Fowler podaje systemy rezerwacyjne, finansowe i logistyczne. Dalszą część książki – i objętościowo większą – stanowi opis poszczególnych wzorców, które mają zastosowanie w budowaniu aplikacji klasy enterprise. Fowler nie jest jedynym autorem poszczególnych rozdziałów.

Aktualność

Więszkość przykładów kodu jest w Javie, mniejsza część w C#. Niektóre przykłady kodu wydawały mi być pisane za bardzo na piechotę, jak ręczne uzyskiwanie połączeń z puli, czy ręczne zarządzanie transakcjami. Ale te przykłady wyglądają tak przecież nie dlatego, aby wypełnić czymś miejsce w książce, a dlatego, ze tak się wtedy, ponad 15 lat temu, pisało. To tylko pokazuje, jak wielki postęp uczyniliśmy od tego czasu. Duet Spring + Hibernate daje nam zarządzać transakcyjnością za pomocą jednej adnotacji, a pulę połączeń w ogóle ukrywa przed typowym programistą.

Część problemów wydajnościowych, które adresują opisy poszczególnych wzorców, rozwiązało się naturalnie samo wraz ze wzrostem mocy komercyjnie dostępnych komputerów osobistych i serwerów. Inne pozostają stale aktualne. Spacery do bazy danych były kosztowne w 2002 roku, i dalej są czymś, na co trzeba stale mieć oko, gdy buduje się złożone systemy.

Dla kogoś ze świata Javy, wiele wzorców podawanych w książce jako wiedza świeża i wymagająca propagowania, jest już w tej chwili przestarzałych. Powodem absolutnie nie jest jednak to, że wzorce te były chybionymi pomysłami, które nie przyjęły się w praktyce. Wręcz przeciwnie! Wiele z tych wzorców zostało zaimplementowanych we frameworkach, których używamy na co dzień, dzięki temu mamy luksus pozostawania ignorantami w kwestii tego, skąd pochodzą pewne koncepcje. Przykładowo, to, co znamy jako entity w frameworku Hibernate, to Row Data Gateway z książki Fowlera. Wzorce Lazy Load, Foreign Key Mapping, Repository i Embedded Value wprost przekładają się na pojęcia znane z Hibernate’a.

Niektóre z wzorców z ksiązki Fowlera nie znalazły szerszego uznania w świecie Javy, jak Active Record – i w tym akurat przypadku może to i lepiej! Jeszcze inne zdezaktualizowały się (np. Two Step View), ponieważ frontend budujemy obecnie za pomocą Angulara, Reacta albo Vue.js, a w 2002 roku HTML-e wypełnione danymi powstawały po stronie serwera. Jeszcze inne doczekały się własnych bibliotek implementujących je, jak Data Mapper, który jest zaimplementowany w MapStrucie, lub Money, który jest zaimplementowany w Joda-Money.

Podsumowanie

Patterns of Enterprise Application Architecture to świetna lekcja historii, pokazująca, jak rodziły się koncepcje, które obecnie często są oczywiste. Czasami widać wręcz pewne idee na ich wcześniejszym etapie rozwoju względem tego, co wiemy obecnie. Niemniej nieraz w czasie lektury miałem wrażenie, że poznaję rozwiązania problemów, które w 2018 roku zostały już skutecznie rozwiązane przez innych.

Z punktu widzenia historii praktyk wytwarzania oprogramowania, to na pewno bardzo ważna książka. Myślę jednak, że dla kogoś, kto chce po prostu szybko poszerzyć praktyczną wiedzę, której użyje w pracy lub w pet projektach, nie jest to pozycja obowiązkowa. Gdybym miał doradzić sobie z przeszłości, czy tę książkę przeczytać, powiedziałbym, że najlepiej odłożyć ją koniec kolejki, nadać niski priorytet, a na razie sięgać po ważniejsze pozycje – a przynajmniej takie, które wydają się ważniejsze.

Książka jest też dostępna w Helionie pod polskim tytułem Architektura systemów zarządzania przedsiębiorstwem. Wzorce projektowe.

Podsumowanie statystyk repozytoriów projektów zgłoszonych do DSP

Oto podsumowanie statystyk repozytoriów projektów zgłoszonych do tegorocznego DSP. Pokazałem je światu na początku kwietnia. Stronę ze statystykami można znaleźć tutaj.

Statystyki nie będą już dłużej uaktualniane, ponieważ konkurs się skończył. Ostatnie uaktualnienie zostało zrobione z danymi na 31 maja 2017 roku. Dodatkowo 1 czerwca dołożyłem filtr, który zawęża wyniki tak, by można było oglądać tylko finalistów DSP. Jeśli czas mi pozwoli, po wyłonieniu dwudziestki, której projekty i blogi zostaną poddane pod publiczne głosowanie, dołożę jeszcze jeden filtr, w którym będzie tylko ta dwudziestka.

Disclaimer

Statystyki nie są doskonałe. Kilka osób, które podały błędne linki do repozytoriów w profilach na DSP, nie ma żadnych statystyk, chociaż robiły commity. Przyjąłem też założenie, że wszyscy będą rozwijali projekt na jednym branchu, a wiem o przynajmniej jednej osobie, która dała mi znać na Slacku, że rozwijała też drugi branch, który nie został podliczony.

Statystyki były zliczane między 1 marca i 31 maja. Statystyki z GitHuba (forki, gwiazdki) pochodzą z 1 czerwca rano.

Statystyki takie jak te mogą nie oddawać sprawiedliwości projektowi. Nie są one wyznacznikiem jakości. Gdyby płacono nam od linijki napisanego kodu, powstawałyby naprawdę złe projekty.

Podium

Niestety, ze względu na to, że wiele osób wrzuca do repozytorium kod bibliotek będących zależnościami, nie można uczciwie określić, jak wygląda klasyfikacja pod względem ilości dodanego w ciągu trwania projektu kodu. W tej kategorii nie będę więc próbował określić, jak wygląda podium.

Ale są inne kategorie.

Commity

W tej kategorii zwycięzcą jest Michał Kortas, który zrobił aż 532 commity w czasie trwania konkursu. Na drugim miejscu jestem ja z 228 commitami. Trzecie miejsce zajął w tej kategorii Michał Sakwa, który zrobił 209 commitów. Zwycięzca w tej kategorii zrobił więcej commitów, niż zdobywcy dwóch następnych miejsc.

Gwiazdki

Jeśli chodzi o gwiazdki na GitHubie, bezapelacyjnym zwycięzcą jest JustinDB autorstwa Mateusza Macieszka. Zwycięski projekt zdobył do zakończenia konkursu 44 gwiazdki. Na drugim miejscu jest Artemis Entity Tracker autorstwa Kamila Dąbrowskiego, który zdobył 16 gwiazdek. Podium z 13 gwiazdkami zamyka gra Evil Slime City autorstwa Mateusza Kupilasa.

Dni z commitami

Jeśli chodzi o regularność commitów, to pierwsze miejsce przypada mi. Commitowałem przez 83 dni z 92 dni, przez które trwał konkurs. Na drugim miejscu znajduje się Michał Sakwa, który commitował przez 60 dni. Na miejscu trzecim jest Paulina Kaczmarek, która commitowała przez 56 dni.

Co ciekawe, podium we wszystkich trzech kategoriach, które, jak sądzę, było sens tu badać, są identyczne dla finalistów i dla ogółu uczestników.

Ciekawostki

Tak jak poprzednio, tak i tym razem, napiszę o paru ciekawostkach, które dają się zauważyć w konkursie.

  • Tylko 23 uczestników konkursu zrobiło ponad 100 commitów, a tylko 80 uczestników – ponad 50 commitów.
  • Trzech finalistów (czyli osób które napisało 20 postów, w tym minimum 10 o projekcie), nie zrobiło ani jednego commitu w czasie trwania konkursu.
  • Jedno repozytorium uczestnika miało ponad 500 MB, 3 kolejne miały ponad 200 MB, a 5 kolejnych – ponad 100 MB. Dla porównania, moje repozytorium miało 3,5 MB.
  • Przynajmniej jedną gwiazdkę w momencie zakończenia konkursu miało 162 uczestników i 68 finalistów. Przynajmniej 5 gwiazdek miało tylko 14 uczestników i 9 finalistów.
  • Kobiety stanowią 9.6% uczestników i 10.8% finalistów.

Zakończenie

I to tyle, jeśli chodzi o temat statystyk projektów DSP. Pozostawię je w internecie w celach historycznych. Jeśli ktoś chce być z nich usunięty, może się ze mną skontaktować mailowo. Zapraszam do przeglądania.

Podsumowanie obecności mojej i Star Trek API w DSP

Oto krótki post podsumowujący mój udział w DSP z projektem Star Trek API.

Postępy projektu

Przez trzy miesiące projekt zdecydowanie posunął się do przodu.

Przez marzec i kwiecień napisałem parsery do sześciu encji: lokalizacje, jedzenie, organizacje, książki, serie książek i kolekcje książek. Przez cały maj przygotowywałem stronę Star Trek API, której odpalenie zaanonsowałem w poprzednim poście. Można ją obejrzeć tutaj.

Dane liczbowe

Przez czas trwania projektu udało mi się napisać 22 posty, z 27, których napisanie założyłem sobie w pierwszym poście. Większość z postów, które nie powstały, nie powstały dlatego, że tematy, które przygotowałem, wydały mi się za słabe, a miałem krótką listę rezerwową.

Wykonałem w sumie 228 commitów. Commitowałem przez 83 dni z 92 dni, przez które trwał konkurs. W kwietniu i w maju commitowałem codziennie. Dodałem do repozytorium ponad 69 tysięcy linii, a usunąłem 20 tysięcy linii. To lepsza średnia, niż w miesiącach poprzedzających konkurs.

Wrażenia z blogowania

Ciężko było mi znaleźć odpowiednią ilość tematów do postów, i to mimo tego, że zaczynałem z okoła 20 tematami na początku marca. Część z nich się jednak wykruszyło, a nowe nie dochodziły tak szybko, jak bym chciał. Miałem tutaj i tak dużo łatwiej, niż ci uczestnicy konkursu, którzy projekty zaczynali z końcem lutego lub na początku marca. Miałem doświadczenia zbierane od października zeszłego roku.

Myślę, że po okresie konkursu będzie sukcesem, jeśli opublikuję na blogu jeden wpis w miesiącu. Tym niemniej, blog zostaje w sieci. Na pewno do czegoś mi się przyda.

STAPI – plany na przyszłość

Star Trek API, jak już zapowiadałem w marcu, będzie kontynuowane. To dalej zadanie, które mnie fascynuje, i projekt, który chcę mieć w swoim portfolio – jako programista i jako fan Star Treka.

DSP – podsumowanie

W samym konkursie brakowało mi jakichś punktów przełomowych i kogoś, kto prowadził by narrację nad całością. Konkurs trwał, były rozmowy na Slacku, obserwowaliśmy swoje projekty i blogi, ale brakowało jednego miejsca, w którym to wszystko by się spinało. Ale być może to tylko moje wrażenie, bo po prostu nie uczestniczyłem zbytnio w żadnych aktywnościach z innymi uczestnikami i wolałem siedzieć w swojej niszy.

A teraz – czekamy na oba głosowania, i oczywiście na galę finałową!

Star Trek API jest już w sieci!

Star Trek API trafiło w końcu do sieci!

Zmiana priorytetów

Ostatni miesiąc poświęciłem w dużej mierze na doprowadzanie do działania strony głównej STAPI. Musiałem napisać dokumentację API, dorobić zliczanie statystyk encji i statystyk użycia endpointów, napisać trochę tekstów na stronę i pozamykać TODO, które oczekiwały na lepsze czasy. Tym niemniej dziś, w dniu końca konkursu, strona jest gotowa do pokazania światu.

Serwer

STAPI zostało postawione na serwerze dedykowanym Kimsufi. Tutaj moje doświadczenie było słabe. Na stronie Kimsufi.pl widnieje przechwałka, że serwer zostanie uruchomiony w 120 sekund. Uruchomienie mojego zabrało prawie 3 dni. Zamówiłem go w piątek po 17:00. 20 minut później wysłałem maila ponaglającego do supportu, który pozostał bez odpowiedzi aż do 14:30 w niedzielę. Wtedy dostałem odpowiedź, że w 120 sekund stawiane są serwery dla powracających klientów, a od nowych zazwyczaj wymaga się dokumentów potwierdzających tożsamość. U mnie obyło się bez wysyłania dokumentów, ale weryfikacja płatności trwała do 16:15 w poniedziałek. Przez cały poniedziałek wysłałem 4 kolejne maile ponaglające, które pozostały bez odpowiedzi.

System

System operacyjny w Kimsufi instaluje się przez webowy panel administracyjny. Na system wybrałem Ubuntu, które znam stosunkowo najlepiej. Na początku zainstalowałem wersję 17.04 (Zesty Zapus), ale ponieważ tutoriale na blogach, z których korzystałem, nie były z nią kompatybilne, cofnąłem się do wersji 16.04 (Xenial Xerus), która jest LTS-em.

Java i Oracle

Po tym, jak zainstalował się system, trzeba było zainstalować Javę 8. To robi się trzema łatwymi do wygooglania poleceniami, więc nie będę się nad tym rozwodził.

Grubszy problem wystąpił z bazą danych Oracle XI 11g, która nie jest oficjalnie wspierana na Ubuntu. Ponieważ jednak wśród wspieranych przez Oracle’a dystrybucji Linuxa nie było żadnej, którą można było zainstalować z panelu Kimsufi, postanowiłem pójść za najlepiej widocznym w Google tutorialem na temat instalacji Oracle’a na Ubuntu, jaki znalazłem. Jedyne, co musiałem zmienić w całej procedurze (napisanej dla Ubuntu 12.04), to sposób zaktualizowania parametrów jądra. Zamiast polecenia sudo service procps start, które nie chciało działać, wykonałem polecenie sysctl --system.

Deployment

Następnie trzeba było zasilić bazę z gotowego dumpu, który wygenerowałem kilka dni temu, oraz wrzucić wygenerowany plik WAR na Tomcata. Na tym etapie życia projektu proces deploymentu jest jeszcze mocno ręczny, ale w przyszłości na pewno go sobie zautomatyzuję. Na tę chwilę z automatu podmieniam tylko propertisy i generuje dumpa z innym schematem, niż schemat źródłowy, ale paczkę i bazę wgrywam z palca.

Problemy

Nic nigdy nie działa od pierwszego strzału, i tak samo było tym razem. Po wrzuceniu WAR-a na Tomcata nie było widać dokumentacji, ponieważ kontrakty nie były kopiowane do paczki. Ponadto okazało się, że część endpointów nie odpowiada, ponieważ ich nazwy były krótsze, niż sześć znaków, i leciał wyjątek, bo nie zabezpieczyłem się przed trywialnym StringIndexOutOfBoundsException.

Podsumowanie

Cieszę się, że udało mi się postawić stronę w ostatniej chwili. Dzięki temu lepiej widać, ile udało mi się zrobić przez ostatnie miesiące, i ile jeszcze pracy przede mną. Dodatkowo mam nadzieję, że sprawdzi mi się podejście release early, release often, i fandom Star Treka zainteresuje się tym projektem, zanim przyjdzie mi skończyć go w pojedynkę. Chociaż wcale się przed tym nie uchylam.

CodeNarc – linter do Grooviego

Gdy zaczynałem pracę nad Star Trek API, naturalnym wydało mi się zastosowanie jakiegoś lintera do Javy. Wybór padł na popularny Chekstyle. To żadne zaskoczenie, ale ponad połowa kodu, który w tej chwili znajduje się w repozytorium projektu, to kod testów napisanych w Spocku, czyli w efekcie w Groovym. Udało mi się znaleźć tylko jeden linter do Grooviego, CodeNarc, który opiszę w tym poście.

Instalacja

Jeśli budujemy projekt za pośrednictwem Gradle’a, jak w moim przypadku, instalacja ogranicza się do dodania do Gradle’a pluginu:

apply plugin: 'codenarc'

I skonfigurowania:

codenarc {
configFile = "$rootDir/codenarc.groovy" as File
toolVersion = '0.26.0'
}

Następnie trzeba stworzyć plik codenarc.groovy w katalogu głównym projektu, lub w dowolnym innym, jeśli wskażemy go w konfiguracji. Gdy to zrobimy, mamy dostępne dwie nowe komendy:

gradle codenarcMain
gradle codenarcTest

Pierwsza komenda sprawdza, czy produkcyjny kod w Groovim jest zgodny z aktywowanymi w CodeNarcu regułami, a druga robi to samo dla kodu testów. Ponieważ w projekcie nie mam żadnego kodu produkcyjnego w Groovym, przy walidacji kodu posługuję się tylko tą drugą komendą.

Dodatkowo teraz, gdy wywołujemy komendę gradle check, wykonywane są też walidacje pochodzące z CodeNarcu.

Konfiguracja

Cała konfiguracja Codenarcu odbywa się we wspomnianym już pliku codenarc.groovy. Alternatywnie można skorzystać z konfiguracji przez plik z propertisami, ale ja wybrałem plik Groovy.

Postanowiłem zacząć od wrzucenia pliku ze wszystkimi regułami domyślnie włączonymi, i po kolei rezygnować z tych, które uznałem za bezcelowe lub niemające znaczenia. Mimo wszystko nie zrezygnowałem z dużej liczby reguł, ponieważ kod testów jest z natury prostszy, niż kod produkcyjny, a więc nie tak wiele rzeczy trzeba było poprawiać, żeby walidacje przechodziły.

Ostatecznie mój plik z regułami wygląda tak.

Wyłączone reguły

Oto wybrane reguły, które wyłączyłem w Star Trek API, wraz z powodami wyłączenia:

  • NoTabCharacter – ponieważ w całym projekcie używam tabów.
  • AbstractClassWithoutAbstractMethod – ponieważ czasami moje klasy abstrakcyjne mają tylko pola, których wartości używane są w testach.
  • TrailingComma – w testach rzadko zmienia się kolejność w listach po ich utworzeniu.
  • ClassJavadoc – zgodnie z regułami clean code’u, piszę bardzo mało dokumentacji do kodu. Jeszcze mniej sensu miałoby pisanie jej w testach, gdzie, IMO, dostateczną dokumentację stanowią nazwy metod testowych pisane językiem naturalnym.
  • SpaceAroundMapEntryColon – wydawało mi się nienaturalne, by konstruktor mapy zapisywać jako [ : ], a nie [:]. CodeNarc jest pierwszym miejscem, gdzie spotkałem się z takim zapisem.
  • MethodSize – domyślnie metoda może mieć sto linii. W przypadku metod testowych nie ma sensu robić wygibasów, by je podzielić, zwłaszcza że są już podzielone przez bloki given, when: i then:.
  • AUnnecessaryObjectReferences – reguła zabrania odwoływania się do jednego obiektu więcej, niż domyślne 5 razy. Często natomiast w testach mam sytuację, gdy robię assercje nawet na kilkudziesięciu polach jednego obiektu. Jako alternatywę CodeNarc podaje zapis with. Ten zapis nie jest niestety wspierany przez moje IDE, więc automatyczny refaktor go nie obejmie, i nie można korzystać w bloku with z podpowiadania składni.

Przekonfigurowane reguły

Następujących reguł nie wyłączyłem, ale je przekonfigurowałem:

  • LineLength – dopuszczalna długość linii została ustawiona na 150 znaków, podobnie jak w Checkstyle’u.
  • BuilderMethodWithSideEffects – CodeNarc traktował wszystkie metody, które zaczynały się od create jako metody wytwórcze, które nie mogą mieć efektów ubocznych. Ja natomiast mam metody testowe, których opis w języku naturalnym zaczyna się od create, więc zmieniłem regułę tak, aby były brane pod uwagę tylko te metody, które zaczynają się od create, po którym następuje wielka litera.
  • PackageName – musiałem dopuścić camel case w nazwach paczek.
  • Najczęściej ignorowane reguły

    Oto lista reguł, które najczęściej ignoruję w pojedynczych metodach lub klasach:

    • LineLength – w niektórych miejscach lepiej nie łamać linii, gdy są za długie, bo czytelność jest gorsza. Używam też tego wykluczenia w przypadku testów z datatables.
    • RuntimeException – w niektórych testach asercje wykonuję w pętlach, więc rzucam z nich wyjątki. Nie widziałem potrzeby tworzenia nowego typu wyjątku tylko na ten przypadek, więc dozwoliłem rzucanie wyjątków typu RuntimeException.
    • BracesForMethod – czasami metoda ma długi opis, i wtedy jest zapisywana z trzema cudzysłowami, a nie z jednym. W tym przypadku trzeba wyłączyć regułę BraceForMethod.

    Czego można się dowiedzieć

    Podczas implementowania CodeNarcu dowiedzieć się można kilku ciekawy rzeczy. Ja dowiedziałem się następujących:

    • Ostatnie domknięcie w ciągu metod może być zapisane bez nawiasów okrągłych. Czyli .do { it.stuff() } zamiast .do({ it.stuff() }).
    • Nie powinno się zapisywać metod voidowych jako def something(), ponieważ def oznacza tyle, co Object, a metody voidowe, takie jak metody testowe, niczego nie zwracają.
    • Nie ma sensu zapisywać stringów w podwójnych cudzysłowach, jeśli nie robimy w nich interpolacji. Także z powodów wydajnościowych.

    Podsumowanie

    Zastanawiam się, ile osób używa CodeNarcu w stosunku do produkcyjnego kodu w Groovim. Na GitHubie ma on zaledwie 120 gwiazdek, w porównaniu do 1985 gwiazdek, które ma Checkstyle.

    CodeNarc na pewno poprawia jakość kodu, który piszemy. Pewnie miałbym z niego więcej użytku, gdybym w Groovim nie pisał tylko testów. Mimo wszystko pomaga, choćby w usuwaniu nieużywanych zmiennych i formatowaniu kodu.

    Najgorsze i najlepsze rzeczy na rozmowach kwalifikacyjnych

    Byłem w życiu na kilkunastu rozmowach kwalifikacyjnych. Kilka z nich zapamiętałem szczególnie, i o nich dzisiaj napiszę.

    Sześć na jednego

    Zdarzyło mi się być na rozmowie kwalifikacyjnej, gdzie umówiony byłem z jedną osobą, a przyszło sześć. Nietrudno się domyślić, że taka przewaga, połączona z faktem, że pytania zadawała mi większość osób, zakończyła się tym, że do firmy się nie dostałem.

    Pytania o rzeczy niepraktyczne

    Zdarzyło mi się więcej niż raz być zapytanym o to, jak zaimplementować algorytm sortowania. Jestem przekonany, że w firmach, gdzie o to pytano, nigdy nie implementowało od zera takich algorytmów.

    Więcej niż raz pytano mnie także o hoisting w JavaScripcie, chociaż użycie dowolnego javascriptowego lintera sprawia, że problem z hoistingiem nie ma prawa wystąpić.

    Pisanie kodu na kartce

    To chyba już klasyka rozmów kwalifikacyjnych. Pisanie kodu na kartce lub na tablicy suchościeralnej. Kartka to nie edytor, skreślenia okropnie wyglądają, kartka i tablica szybko się kończą, a mój mózg jest nieprzyzwyczajony do pisania kodu w ten sposób, bo nigdy tak tego nie robię.

    Nie rozumiem, dlaczego problemem jest przyjście na rozmowę kwalifikacyjną z laptopem ze skonfigurowanym środowiskiem, i użycie go do rozmowy kwalifikacyjnej. Z drugiej strony Jakub Nabrdalik mówił o tym, że częścią procesu rekrutacyjnego może być całodniowy pair programming z kandydatem. Są więc firmy, gdzie kartka nie ma prawa bytu.

    Byłem raz umówiony na rozmowę kwalifikacyjną, ale w jednym z późniejszych maili dowiedziałem się, że połowa dwugodzinnej rozmowy to test pisemny. Zrezygnowałem z pójścia na tę rozmowę.

    Podchwytliwe pytania

    Deprymujące są również podchwytliwe pytania. Mają one albo za zadanie udowodnić, że nie powinienem chcieć za wiele pieniędzy, skoro przyjdzie mi pracować z takimi wybitnymi postaciami, albo też po prostu uwalić kandydata. Byłem raz na rozmowie, gdzie jedna z trzech osób rekrutujących mnie była ewidentnie od początku na nie. Oczywiście się nie dostałem.

    Najlepsze są rozmowy o technologii

    Dwie najlepsze rozmowy, jakie miałem, nie były testami. Nie wydawało się też, żeby miały określony scenariusz, a jeśli tak, to był on realizowany dyskretnie. Moje dwie najlepsze rozmowy kwalifikacyjne były po prostu luźnymi rozmowami o programowaniu i architekturze, gdzie moje odpowiedzi w dużej mierze wyznaczały tematykę kolejnych pytań. Po obu tych rozmowach dostałem propozycje pracy. Z jednej ostatecznie skorzystałem.

    Podsumowanie

    Jak się okazuje, mocne wspomnienia mam głównie z nieudanych rozmów kwalifikacyjnych.

    Nie twierdzę, że wiem, jak powinny być przeprowadzane rozmowy kwalifikacyjne, ale wydaje mi się, że jest kilka rzeczy, których należałoby unikać. Na pewno należą do nich testy na kartce i pytania o rzeczy, o których wiedza nie ma w firmie żadnej praktycznej wartości.

    EhCache i Hibernate

    Planuję doprowadzić development Star Trek API do końca. Będę przez jakiś czas, być może całkiem długi czas, jedyną osobą, która będzie płaciła za utrzymanie serwera, na którym API będzie działało. Nie planuję inwestować w taki serwer zbyt wielu środków, dlatego w moim interesie jest, by zrealizować wydajne cache’owanie. Przyda mi się ono na pewno, biorąc pod uwagę fakt, że do tej pory do Pokéapi zostało wykonane 175 milionów zapytań.

    Wymagania

    Wymagania dotyczące cache’u w Star Trek API są relatywnie proste, w porównaniu do innych systemów. Dane, które trafią do bazy, czeka jeden z dwóch scenariuszy. W pierwszym scenariuszu dane nie będą się zmieniać, gdy już trafią do bazy, ponieważ zasilenie dokona się poza serwerem aplikacyjnym, i serwerowi aplikacyjnemu zostanie tylko wskazana napełniona baza. Drugi scenariusz jest taki, że dane zmienią się co prawda, ale będę mógł kontrolować moment, gdy się to stanie, i wówczas będzie można wyczyścić cache. Problem inwalidacji cache’y w zasadzie nie będzie istniał.

    Instalacja i konfiguracja

    A móc używać EhCache, po pierwsze trzeba dodać do projektu zależności. Ja dodałem dwie:

    compile group: 'net.sf.ehcache', name: 'ehcache'
    compile group: 'org.hibernate', name: 'hibernate-ehcache'

    Po drugie, trzeba skonfigurować przynajmniej domyślny cache. EhCache pozwala na składowanie cache’owanych zasobów w wielu regionach, ale zdecydowałem na początek, że przynajmniej póki nie będę wiedział więcej o tym, jakie zasoby są najczęściej kwerendowane, nie chcę przekombinować z konfiguracją. Moja obecna konfiguracja wygląda więc tak, i jest najbardziej podstawową wersją, jaka może być.

    Integracja z Hibernatem

    EntityManagerFactory musi dostać kilka dodatkowy propertisów, widocznych tutaj. Trzeba więc między innymi włączyć cache drugiego poziomu, a także wskazać EhCache jako fabrykę regionów cache’u. Oznacza to klasę implementującą interfejs zdefiniowany przez Hibernate’a, który gwarantuje wytworzenie kilku kolejnych obiektów, odpowiedzialnych kolejno za cache’owanie między innymi kolekcji, wyników zapytań, i encji.

    Strategia cache’owania

    Wymyśliłem, że dodatkowo będę miał własną strategię cache’owania. Interfejs jest prosty i wygląda tak. Strategia dostanie QueryBuilder, czyli obiekt, w którym przed wykonaniem zapytania gromadzone są jego parametry (kryteria zawężające, sortowanie, relacje do pobrania). Strategię cache’owania można zdefiniować w propertisach aplikacji. Napisałem trzy strategie. Jedną, która zawsze zwraca prawdę, druga, która zawsze zwraca fałsz, i trzecią, najciekawszą, której chce domyślnie używać, i która zwraca prawdę warunkowo, gdy jedynym kryterium w zapytaniu jest UID, czyli unikalny identyfikator obiektu.

    Cache’owanie encji

    Gdy już mamy wszystko zintegrowane, trzeba wyznaczyć encje, które będą cache’owane. W moich przypadku są to wszystkie encje, które trafiają do REST-owego i SOAP-owego API. Do encji trzeba było dodać adnotację @Cache, tak jak zostało zrobione tutaj. Dodatkowo napisałem test, który z pomocą refleksji pilnuje, żeby wszystkie encje, poza jawnie określonymi, miały zawsze adnotację @Cache. Napisałem też drugi test, który to samo robi dla pól zawierających relacje jeden do wielu i wiele do wielu.

    Testy

    Pozostało mi przetestować ten setup. Testy manualne polegały na zapytaniu kilku endpointów i obserwowaniu, jakie zapytania odkładają się w logach za pierwszym i za kolejnymi razami. Za kolejnymi razami było tych zapytań zdecydowanie mniej, chociaż ciągle jeszcze jakieś się pojawiają i będę musiał wrócić do tematu, żeby maksymalnie wyżyłować EhCache. Prędkość odpowiedzi endpointów za kolejnymi zapytaniami znacząco wzrosła.

    Podsumowanie

    Przewiduję, ze klienty będą wykonywać relatywnie mało zapytań przeszukujących bazę, w których zestaw kryteriów zapytania jest słabo powtarzalny, a relatywnie dużo zapytań, które pytają o konkretny zasób za pośrednictwem UID. To założenie może z łatwością okazać się nieprawdziwe, a wtedy trzeba będzie przestrajać cache. Ale to może być temat na kolejny post, za kilka miesięcy.

    Powody mojego odchodzenia od frontendu

    W ciągu ostatnich miesięcy coraz bardziej wzbraniam się przed wykonywaniem zadań frontendowych. Nie wzbraniam się oczywiście w pracy, bo tam nie mam luksusu wybierania sobie tasków, ale jeśli chodzi o projekty po godzinach, to wybieram takie, gdzie jest możliwie mało frontendu. Star Trek API jest przykładem takiego projektu, do którego dopiero w ostatnich dniach dopisuję jakikolwiek frontend, a wcześniej był do tylko kod produkcyjny w Javie i testy w Groovim.

    W tym poście opiszę, jakie są powody mojego odchodzenia od frontendu.

    Ciekawsze problemy

    Problemy rozwiązywane na backendzie są ciekawsze. Złożone operacje biznesowe, wielkie zbiory danych, współbieżność, modelowanie domeny – to wszystko odbywa się na backendzie. Frontend, w idealnym świecie, dba tylko o odbieranie i zapisywanie danych za pośrednictwem wystawionego przez backend API o uproszczonym modelu. Można się spotkać z praktyką realizowania logiki biznesowej na frontendzie, ale abstrahując od wszystkich innych powodów, by tak nie robić, jeden wydaje mi się przesądzający. Nie należy rozbijać realizacji logiki biznesowej na frontend i backend, ponieważ nie sposób wyznaczyć, co powinność być realizowane na frontendzie, i dlaczego.

    Mniejsza powtarzalność

    Wolę nie robić w kółko tego samego. Wolę nowe rzeczy, i nie tylko jeśli chodzi o implementację nowych funkcjonalności, którą preferuję ponad realizację utrzymania istniejącego kodu. Wolę nowe rzeczy także jeśli chodzi o rozwiązywanie nowych problemów. A problemy na frontendzie mogą być powtarzalne, przynajmniej jeśli realizuje się typowy frontend dla aplikacji B2B albo B2C. Trzeba pociąć layout z PSD. Trzeba napisać kontroler w Angularze lub w innym frameworku. Kontroler pobiera jakieś dane, i może jakieś zapisuje. Trzeba poprawić CSS-y, bo znów inaczej wyglądają na Chromie i na IE. I tak dalej.

    Upraszczam tutaj problem, ale wyzwania frontendowe to pewna dość wąska specjalizacja, w której łatwo popaść w rutynę. Są naturalnie ciekawe wyzwania frontendowe, ale jest ich relatywnie mało, przynajmniej takich, które bym nie pociągały. A pociągałaby mnie np. praca przy frameworkach do wizualizacji danych. Na pewno byłaby trudna, ale dająca satysfakcję.

    Technologia

    Na frontendzie nie ma alternatywy dla JavaScriptu. Jest długa lista języków i dialektów, które transpilują się do JavaScriptu, ale ostatecznie, w przypadku debuggowania kodu, i gdy zawodzą sourcemapy, zmuszeni jesteśmy do przedzierania się przez sieczkę, jaką jest kod wygenerowany przez transpiler.

    JavaScript ciągle jest dość okropnym językiem, nawet jeśli piszemy w ES6. Ilość problemów nierozwiązanych do tej pory w JavaScripcie, a rozwiązanych lub nigdy nie istniejących w innych językach skłania mnie do myśli, że JS nigdy nie zostanie naprawiony, bo ciągnie za sobą ogon zbyt wielu złych decyzji podjętych na początku jego istnienia.

    Ekosystem

    Ekosystem javascriptowy jest pełen ciągle nowych frameworków, pluginów do tych frameworków, integracji między tymi frameworkami i tak dalej. Duże frameworki są finansowane przez biznes, któremu zależy na ich jakości, ale wokół nich wypączkowują różne mniejsze inicjatywy, często ciągnięte do przodu tylko przez jedną osobą. Często ta jedna osoba traci w końcu zainteresowanie swoim projektem, więc mamy bardzo dużo średnio działającego, niezbyt aktualnego, marnie napisanego oprogramowania. Kto nie wierzy, niech zobaczy na GitHubie, jak są napisane javascriptowe projekty, które mają tysiące gwiazdek. Wiele z nich nie przeszło by nigdzie wewnątrzfirmowego code review, gdyby podobny kod powstał w Javie albo C Sharpie.

    I to kolejny problem JavaScriptu. Brak jest w tym ekosystemie znajomości i chęci stosowania dobrych praktyk, jeśli chodzi o wytwarzanie oprogramowania. Ciągle powstaje dużo kodu bez testów, stosowanie wzorców projektowych jest sporadyczne, a kod spaghetti ciągnie się kilometrami.

    Podsumowanie

    Nie jest tak, że narzekam na frontend, bo go nie znam. Przeciwnie, w obecnej firmie zostałem zatrudniony w dużej mierze ze względu na kwalifikacje frontendowe, bo na Javę dopiero chciałem się przekwalifikować. Po prostu, po tych paru latach, frontend mało mnie już ekscytuje, a na backend ciągle mam ochotę, zarówno jeśli chodzi o poszukiwanie nowych wyzwań zawodowych, jak i o poszukiwanie nowych tematów na projekty realizowany po godzinach.

    Różnice między modelem z uniwersum Star Treka i modelem z prawdziwego świata

    W trakcie pracy nad Star Trek API spotykam dwa rodzaje modeli. Jedne to modele dotyczące uniwersum Star Treka, a drugie to modele dotyczące prawdziwego świata. Do modeli dotyczących uniwersum zaliczyć można lokalizacje, obiekty astronomiczne, jedzenie czy statki kosmiczne, a do modeli z prawdziwego świata – filmy, aktorów, komiksy czy firmy pracujące nad produkcją. Istnieją między nimi różnice, które opiszę w tym poście.

    Ilość

    Pierwsza różnica to różnica w ilości modeli. Tych z prawdziwego świata jest trochę mniej. Obecnie mam ich zrealizowanych 13, a zaplanowanych kolejne 6. W przypadku modeli z uniwersum zrealizowanych jest 6, a zaplanowanych do realizacji – kolejne 17. Postęp moich prac można śledzić tutaj. Także ilość krotek w modelach różni się. Osób pracujących nad Star Trekiem jest ponad 6000, a aktorów, kaskaderów i innych wykonawców widocznych na ekranie – ponad 5000. Jeśli chodzi o modele z uniwersum, najwięcej, około 5000, jest postaci, a potem są już obiekty astronomiczne i miejsca, jedne i drugie z wynikiem poniżej 2000 krotek.

    Relacje

    Modele z prawdziwego świata mają znacznie więcej relacji, niż modele z uniwersum, i są one znacznie lepiej uporządkowane. Przykładowo, ksiązki mają aż 11 relacji wiele do wielu: serie książek, autorów, artystów, edytorów, narratorów audiobooków, wydawców, wydawców audiobooków, postacie, identyfikatory wydań książek (ISBN, ASIN) i identyfikatory wydań audiobooków, oraz kolekcje książek. Modele z uniwersum, które mają najwięcej relacji, to obiekt astronomiczny, seria książek i seria komiksów. Mają one relacje do nadrzędnego elementu i do podrzędnych elementów tego samego typu. Większość modeli z uniwersum nie ma żadnych relacji.

    Modele z uniwersum mają jednak na ogół więcej flag booleanowych. Każda kategoria, np. dotyczące lokalizacji, może być przetłumaczona na jedną flagę w modelu. I tak w encji Localization znajdujemy takie flagi, jak bodyOfWater, colony czy school. Większość flag dla większości krotek będzie naturalnie ustawiona na fałsz.

    Kompletność

    Model z prawdziwego świata ma zazwyczaj więcej danych, niż model z uniwersum, z oczywistego powodu: wydarzenia muszą mieć daty, ludzie nazwiska, a książki zawsze określoną ilość stron. W przypadku modeli z uniwersum wiemy tylko tyle, ile powiedziano w kanonicznych produkcjach. Najczęściej jest tak, że jakiś byt dający się ująć w model występuje w serialu przez chwilę, jest wspomniany raz, i nigdy więcej nie zostaje potem użyty. Przykładem niech będzie ten kawior, o którym wiemy tylko tyle, że był słony, ale nawet ta informacja jest zbyt egzotyczna, by ująć ją w model.

    Model z prawdziwego świata można próbować uzupełniać w innych źródłach. Można spróbować zgadnąć płeć aktora na podstawie płci jego imienia lub znaleźć jego datę urodzenia w IMDb. W przypadku modelu z uniwersum, który nie ma kompletnych danych, można tylko liczyć, że dany byt zostanie jeszcze w przyszłości wspomniany w jakimś medium.

    Podsumowanie

    Mogąc wybierać między modelami z prawdziwego świata i tymi z uniwersum, wolę pisać kod parsujący te drugie. Jest to łatwiejsze, szybsze i odkrywam wówczas model, który jakoś nakłada się na moje wspomnienia z oglądania kolejnych sezonów Star Treka. Ale żeby API było kompletne, nic nie może zostać potraktowane po macoszemu. Z drugiej strony, przez ostatnie miesiące częściej brałem się za modele z prawdziwego świata, niż za modele z uniwersum. Robiłem tak dlatego, żeby nie stracić zainteresowania projektem i najlepsze zostawić sobie na koniec.

    Stałe ID obiektu w API przy zmieniającym się ID w bazie danych

    Podczas tworzenia Star Trek API szybko doszedłem do wniosku, że nie mogę ujawniać klientom API ID-ków bazodanowych. Powodem było to, że ID-ki te nieustannie się zmieniały. Ilekroć robiłem zasilenie od nowa, było niemal pewne, że dostanę nieco inne zestaw stron, które zostaną przetworzone w innej, niż poprzednio, kolejności. Wydało mi się oczywiste, że gdybym sam chciał skorzystać z danych tego typu, jak udostępniane przez moje API, oczekiwałbym, że ID będą stałe. Chciałbym je gdzieś zapisywać i posługiwać się nimi w przyszłej komunikacji z API.

    Zadanie

    Trzeba było wymyślić sposób na to, aby generować stałe, lub przynajmniej w ogromnej większości przypadków stałe identyfikatory dla wszystkich encji. Dodatkowym bonusem byłoby to, aby identyfikatory były unikalne w obrębie całego API.

    Wykonanie

    Memory Alpha i Memory Beta mają własną numerację stron, którą na szczęście ujawniają. Każda strona ma ID, który nie zmienia się przez cały cykl jej życia, nawet wówczas, gdy strona zostanie przeniesiona pod inną nazwę. Identyfikatory stron z z wiki mogły więc zostać pierwszą ważną częścią identyfikatora, który już wówczas zacząłem nazywać GUID-em, a któremu ostatnio zmieniłem nazwę na UID, by nie było wątpliwości, że to co innego, niż GUID.

    Od razu było jasne, że potrzebuję jeszcze czegoś, co identyfikowałoby encję w unikalny sposób. ID-ki z Memory Alpha i Memory Beta mogły się powtarzać. Do UID-ów dołączyłem więc 2 znaki, które reprezentowały encję, oraz 2 znaki, które reprezentowały źródło. W ten sposób powstał na przykład UID, który wyglądał tak: CLMA0000108985. Dwa pierwsze znaki, CL, oznaczały encję ComicCollection, dwa kolejne, MA, oznaczały, że źródłem jest Memory Alpha. Natomiast 10 cyfr to ID z wiki. Razem zawsze 14 znaków, co wydało mi się wystarczające na API tej wielkości.

    Pierwsze, i na razie jedyne, kłopoty

    Pierwszy i na razie jedyny problem, który napotkałem, dotyczył bytów, które nie mapowały się na dokładnie jedną stronę z wiki. Chodziło o encje reprezentujące numery ISBN i ASIN. Na jednej stronie mogło występować kilka takich numerów.

    Na szczęście numery ISBN mają 10 lub 13 znaków, a numery ASIN – 10 znaków. Łatwo było z nich wygenerować 14-znakowe UID-y. 10-znakowym numer ISBN mają po prostu format ISBN160010603X, 13-znakowe ISBN-y format I9781566199094, a 10-znakowe numery ASIN-y – format ASINB008L4YH4W. Można więc w dalszym ciągu zagwarantować unikalność UID-u w systemie.

    Implementacja

    Ostatecznie wszystkie te wymagania zostały zaimplementowany w klasie UidGenerator. Klasa jest wołana zawsze, zanim encja zostanie pierwszy raz zapisana, i w ten sposób UID zawsze jest obecny.

    UID-y zastępują w API bazodanowe identyfikatory. To nimi pyta się o pojedyncze REST-owe i SOAP-owe obiekty. UID-y też, wraz z nazwami lub tytułami, trafiają do encji nagłówkowych, takich jak MovieHeader. Na ich podstawie, gdy ktoś będzie chciał, będzie mógł pobrać więcej danych na temat interesującej encji.

    UidGenerator podczas inicjowania aplikacji sprawdza, czy będzie potrafił wygenerować unikalne identyfikatory dla wszystkich encji. Jeśli nie, trzeba dodatkowo dodać identyfikator skrócony identyfikator encji w statycznej mapie. W nieprawdopodobnym przypadku, gdy ID strony z Memory Alpha lub Memory Beta będzie większy, niż 9999999999, zostanie rzucony wyjątek, którym będzie trzeba się zająć. Ale to temat na odległą przyszłość.