Zasada Pareta i pozyskiwanie danych

Pozyskiwanie danych z Memory Alpha do Star Trek API to zarówno próba odkrycia wszystkich reguł i konwencji, które przyjęli ludzie, którzy przez lata tworzyli to wiki, jak i konieczność zmierzenia się ze wszystkimi niekonsekwencjami, które przy tworzeniu tak dużego wiki musiały się pojawić, oraz z wszystkimi tymi przypadkami, gdzie konwencje nie zostały wypracowane.

Odkrywanie danych

Odkrywanie danych i wielości formatów, w których są prezentowane, odbywa się stopniowo i przynajmniej początkowo nie wiąże się z pisaniem kodu. Najpierw po prostu przeglądam kilkadziesiąt lub sto kilkadziesiąt stron z kategorii źródłowych, ze szczególnym zwróceniem uwagi na zawartość szablonów i na kategorie, w których jest strona, żeby zyskać ogólne pojęcie o jakości i kompletności danych. Po tym wiem, które pola w szablonach są używane i jak często. Zdarza się bowiem, że pewne pola w szablonach, chociaż obecne, są wykorzystywane na przykład w 1% przypadków. Wówczas trzeba podjąć decyzję, czy w ogóle uwzględniać taką informację w modelu.

Gdy mam już z grubsza opracowaną listę pól w szablonie, z których będą pozyskiwane dane, tworzonę często zbiorczy procesor, podobny do ComicsTemplatePartsEnrichingProcessor. Taki procesor deleguje parsowanie zawartości poszczególnych pól szablonu do swoich zależności.

Standard i jego brak

Tym, co jako pierwsze zazwyczaj implementuję, jest jakiś szeroko rozpowszechniony na Memory Alpha standard zapisu danej informacji, np. zapis daty gwiezdnej w formie 4506.4 – 4512.2. To pokrywa większość przypadków. Następnie, w tym samym procesorze, po prostu loguję wszystkie kolejne niepuste wartości, których nie udało się sparsować pierwszym sposobem, i szukam kolejnych reguł, które można by zaimplementować.

Tutaj najlepiej widać zasadę Pareta. Najczęstszy format jest zarówno łatwo zrozumiały dla ludzi, jak i łatwy w implementacji, a w miarę, jak odkrywam kolejne zapisy tego samego typu informacji, są one zarówno mniej intuicyjne, jak i nieco trudniejsze w implementacji.

W końcu redukuję listę możliwych formatów danej informacji do grupy niepowtarzalnych. Są to zarówno zanieczyszczone informacje, których parsery byłoby czasochłonne w pisaniu i niemożliwe do ponownego użycia, jak i niepoprawnie zapisane informacje, które należałoby poprawić na Memory Alpha. Zdarzają się też informacje, których nie można dopasować do modelu, chociaż są łatwo zrozumiałe dla ludzi.

Po tym wszystkim pozostaje zaimplementowanie pozostałych, dających się dopasować do modelu danych, jako FixedValueProvider, np. taki jak ComicSeriesTemplateNumberOfIssuesFixedValueProvider.

Nie ma sensu pisać kodu

Dla pewnych typów danych nie ma sensu pisać procesora mierzącego się z treścią wiki. Przykładem jest tu SeriesEpisodeStatisticsFixedValueProvider, klasa zawierająca zapisane na sztywno statystyki seriali dotyczące ilości sezonów, ilości odcinków i ilości podwójnych odcinków. Stwierdziłem, że dla siedmiu serialu nie ma w ogóle sensu pisać kodu w Javie, i szybciej dostarczę te wszystkie statystyki w ten sposób. To skąpe i rzadko zmieniające się informacje. Myślę, że napisanie rusztowania dla procesora, który wyciągałby te statystyki bez zapisywania na sztywno, zajęłoby mi pewnie 10 lub 15 minut, czyli tyle, ile zajęło mi zapisanie tego na sztywno.

Podsumowanie

Odkrywanie danych w zmieniającym się źródle danych nigdy nie jest skończone. Mogą bowiem pojawiać się nowe strony, a stare mogą zmieniać zawartość. Dlatego ważne jest, żeby wszystkie te przypadki, gdy danych nie udało się sparsować, logować do konsoli w celu dalszej inspekcji i ewentualnej poprawy w przyszłości.

Coroczna ankiety Stack Overflow dla developerów

Ten post to subiektywny wybór informacji z corocznej ankiety skierowanej do developerów odwiedzających Stack Overflow. Podobnie jak w przypadku poprzedniego postu z ciekawostkami na temat procesu wytwarzania oprogramowania w Google, ten post nie pretenduje do bycia kompleksowym opisem tej ankiety, a jest jedynie wyborem tego, co wydawało mi się interesujące.

Zasięg ankiety

  • W 2016 roku ankieta dotarła do 56 tysięcy osób, a w 2017 roku do 64 tysięcy. To wzrost o 14%.
  • W 2016 roku 92.8% respondentów określiło swoją płeć jako meską, a 5.8% jako żeńską. W 2017 roku 88.6% respondentów określiło swoją płeć jako męską, a 7.6% jako żeńską.
  • Nieco więcej ludzi z Europy, niż z Azji odpowiedziało na ankietę, proporcjonalnie do ilości osób, które odwiedzają Stack Overflow.

Rasa

  • W 2017 po raz pierwszy zapytano respondentów o rasę. 74% respondentów to biali, a kolejne 14% to mieszkańcy Azji. Niedoreprezentowani są mieszkańcy Bliskiego Wschodu (3.6%) i Afryki (2.5%).
  • W krajach latynoskich 81% respondentów określa się jako webdeveloperzy, w porównaniu do 72-74% wśród białych i mieszkańców Azji.
  • Wśród respondentów z niektórych afrykańskich krajów nie było nikogo, kto by pracował zawodowo jako programista.

Języki i technologie

  • Języki programowania znajdujące się w czołówce popularności umocniły swoja pozycję rok do roku. JavaScript, Java i C# zyskały od 3 do 7 pp. Również znajdujący się w czołówce SQL zyskał 2 pp. Python przegonił PHP ze wzrostem o 7 pp. PHP zyskał 2 pp. Tylko Objective-C stracił na popularności, ale w granicach błędu statystycznego (0.1 pp).
  • Rust utrzymuje się na szczycie listy najbardziej kochanych języków, ale spadł o 6 pp. w porównaniu z poprzednim rokiem.
  • Visual Basic pozostaje najbardziej znienawidzonym językiem, a Sharepoint najbardziej znienawidzoną technologią.
  • Node.js znajduje na drugim miejscu najbardziej pożądanych technologii (spadek z pierwszego miejsca). 22% respondentów chce się go nauczyć, ale aż 37% zalicza go do znienawidzonych technologii. Podobnie skrajne opinie wyrażane są o Angularze. Jest to technologia pożądana przed 19% respondentów i znienawidzona przez 48%.
  • CoffeeScript jet technologią, której użycie jest silnie skorelowane z Rubym. Oracle jest silnie skorelowany z Javą. .NET jest skorelowany z SQL Serverem, a PHP z MySQL-em. Tylko korelacja MongoDB z Node.js jest dla mnie zaskoczeniem w tym zestawieniu.
  • Aż 6% developerów uważa, że ilość linii napisanego kodu jest dobrą miarą ich wydajności, a 9% uważa, że dobrą miarą wydajności jest częstotliwość commitów. 41% uważa, że miarą ich wydajności jest wydajność produktu, który tworzą.
  • Domain Driven Design znalazł się wśród praktyk wytwarzania oprogramowania, takich jak agile i kanban, z wynikiem ponad 13%.
  • W zestawieniu technologii kontroli wersji zabrakło Perforce’a (być może zmieścił się w 3-procentowej kategorii „inne”). W zestawieniu znalazł się za to ZIP-y z wynikiem 2%. 3% ludzi nie wersjonuje swojego kodu.

Praca

  • Między 2016, a 2017 rokiem zwiększyła się zarówno ilość osób pracujących na pełen etat (z 67% do 70%), jak i ilość osób, które nie posiadają pracy (wzrost z 2% do 10%). Połowa z nieposiadających pracy nie szukaj jej. Zastanawiam się, czy tak duży wzrost może być spowodowany tym, że zamieniono negatywnie nacechowany termin unemployed, który występował w ankiecie z 2016 r., na neutralny termin not employed, który można znaleźć w tegorocznej ankiecie.
  • Liczba firm zatrudniających do 10 osób spadła o 7 pp. rok do roku, za to nie zmieniła się liczba firm zatrudniających 10-19 osób i 20-99 osób (cały czas odpowiednio 10% i 22%). Ilość firmy powyżej 10 000 pracowników urosła z 12% do 14.6%.
  • Między 2016 a 2017 znacząco wzrosła liczba osób, które określają się jako devopsi – z 2.2% do 11.1%. Podobnie z ludźmi, którzy określają się jako administratorzy systemów. W zeszłym roku było to 1.5%, a w tym 11.2%.
  • Zmniejsza się liczba zdalnych programistów. W 2016 12% respondentów deklarowało się jako zdalni programiści, a w 2017 – 11%. W 2016 roku 22% respondentów deklarowało, że nigdy nie pracuje zdalnie, a w 2017 roku już 32%. Możliwość pracy zdalnej jest skorelowana z satysfakcją z pracy.
  • W kwestii zadowolenia z posiadanego sprzętu do pracy, programiści oceniają poszczególne komponenty bardzo równo. Monitory i ekrany są oceniane najniżej (3.72/5), a prędkość i pojemność dysków najwyżej (3.78/5). W środku stawki są ilość RAM-u (3.76/5) i prędkość CPU i GPU (3.73/5).
  • Tylko 0.8% programistów uważa, że są o wiele za wysoko wynagradzani za swoją pracę. 55% uważa, że są wynagradzani za nisko.
  • Programiści Lispu i Smalltalka najczęściej uważają, że zarabiają za mało. Najrzadziej uważają tak programiści Clojure i Julii. To ciekawe, gdy ma się na uwadze, że Clojure był mocno wzorowany na Lispie.
  • W Polsce więcej osób pracuje zdalnie, niż w Indiach (odpowiednio 10.7% i 9.7%).
  • Administratorzy i ludzie związani z grafiką częściej niż inni pracują zdalnie.

Nawyki i praktyki

  • W tegorocznej ankiety wynika, że taby preferuje 43%, a spacje 38% programistów. 19% używa jednego i drugiego.
  • Z 57% do 53% zmniejszyła się liczba osób, które w pracy wykonują wiele commitów w ciągu dnia, za to zwiększyła się liczba osób, które commitują kilka razy w tygodniu lub kilka razy w miesiącu.
  • W obu latach satysfakcja z pracy była skorelowana z częstotliwością commitów. Im częstsze commity, tym bardziej satysfakcjonująca praca. Podejrzewam, że może się to wiązać z tym, że częściej commitują ci, którzy robią nowe projekty, a rzadziej ci, którzy są w projektach utrzymaniowych.
  • W 2017 roku dokumentacja online była głównym źródłem informacji dla programistów. Wyprzedziła jednak Stack Overflow tylko o 0.1 pp.
  • 33% programistów odpowiedziało, że udziela się w projektach open source. Wydaje mi się to zawyżoną liczbą.
  • 19% programistów nie programuje hobbistycznie.
  • 60% respondentów zgadza się ze stwierdzeniem, że na produkcje może trafiać nieoptymalny kod, który zostanie z czasem poprawiony.

Inne

  • 1% developerów określa się jako niewidomi.

Podsumowanie

Coroczna ankieta Stack Overflow potwierdza wiele stereotypów i powszechnej wiedzy o programowaniu i programistach. Sharepoint jest nielubiany, agile jest popularny, nigdy nie jesteśmy zadowoleni z naszych komputerów i uważamy, że zarabiamy za mało. Z drugiej strony ankieta dostarcza nam informacji o tym, że istnieje grupa wybijających się technologii, na które warto zwracać uwagę. Z powodu uwielbienia, jakim darzą go programiści, Rust jest językiem, któremu warto się przyjrzeć. Podobnie Go – najlepiej obecnie opłacany język w USA.

Całość tegorocznej ankiety można znaleźć tutaj.

Gdy heurystyka wie lepiej

Jedną z danych ekstrahowanych z Memory Alpha jest płeć ludzi pracujących przy Star Treku, zarówno tych występujących jako aktorzy, statyści i kaskaderowie, jak i tych, których nie widać na ekranie. Proces określania płci składa się z kilku podkroków i zamknięty jest w tym procesorze.

Metody nieheurystyczne

Pierwsze, co robi ten procesor, to sprawdza, czy płeć dla danej osoby nie została zapisana na sztywno w słowniku. Słownik powstał, bowiem po tym, gdy wszystkie inne procesory zostały już napisane i przetestowane w boju, ciągle pozostawała mi jakaś lista stron na Memory Alpha, z których nie udawało się wyciągnąć płci. Płeć czasami można było wyczytać z opisu, ale zdarzało się też, że trzeba było ją określać na podstawie zdjęcia, lub na podstawie innych źródeł w internecie. Zdarzało się też, że informacje były na tyle skąpe, że na ich podstawie nie dawało się nic ustalić. Wówczas należało po prostu ustawić płeć na null i nie parsować strony dalej.

Podejmowana jest także próba znalezienia płci na podstawie płci postaci, którą aktor grał. To nie sprawdza się w przypadku personelu pracującego nad produkcją Star Treka, chyba że osoby takie były także aktorami lub statystami.

Jeśli zawiodą wszystkie inne metody, podejmowana jest próba zapytania zewnętrznego API o płeć imienia. Używam tutaj serwisu Genderize.io, który pozwala wykonać 1000 darmowych zapytań dziennie. Pełne zasilenie Star Trek API to od 150 do 200 zapytań do Genderize.io, więc limit jest więcej niż wystarczający. Genderize.io wraz z płcią imienia zwraca także prawdopodobieństwo, że imię przynależy do danej płci. Uznałem, że prawdopodobieństwo na poziomie 95% jest wystarczające, żeby zaakceptować wynik i nie parsować strony dalej. Jeśli także zapytanie do Genderize.io się nie powiedzieć, lub prawdopodobieństwo określenia płci na podstawie imienia będzie zbyt niskie, cały procesor odpowiedzialny za znalezienie płci zwraca null.

Heurystyka

Zanim zostanie podjęta próba znaleziania płci w zewnętrznym API, dokonywana jest analiza na podstawie zaimków i innych rzeczowników znalezionych na początku strony. Heurystyka określająca płeć na podstawie zaimków i innych rzeczowników jest prymitywna. Najpierw pobierane są 3 pierwsze paragrafy strony, a następnie wyszukiwane są słowa takie jak actor, himself, stuntman dla płci męskiej oraz actress, herself, stuntwoman dla płci żeńskiej. Ilość wystąpień jest podsumowywana, i jeśli wystąpień słów wskazujących na płeć męską jest więcej, niż wystąpień wskazujących na płeć żeńską, wtedy zwracana jest płeć męska. Analogicznie dla płci żeńskiej. Dodatkowo, jeśli wystąpień słów z jednej grupy nie jest przynajmniej 2 razy więcej, niż wystąpień słów z drugiej grupy, fakt ten jest logowany.

Mimo doskonalenia w kolejnych iteracjach heurystyki do określania płci, zauważyłem, że jeden aktor, Michael Eugene Fairman, człowiek o wybitnie męskim imieniu, konsekwentnie był prezentowany w logach jako ten, który ma płeć żeńską. Wszedłem więc do artykułu, który go opisywał. Tam, na zdjęciu, dalej wyglądał na płeć męską. Dopiero wczytanie się w treść ujawniło, że:

Since 21 January 2010, Fairman is legally known as Marie Michael Fairman following her transgender idenfication.

I tak, ku mojemu zaskoczeniu, heurystyka zadziałała, bowiem reszta artykułu opisywała aktora w formie żeńskiej.

Przegląd praktyk wytwarzania oprogramowania w Google

Ten post to subiektywy wybór z pracy Software Engineering at Google autorstwa Fergusa Hendersona, która jest przeglądem praktyk wytwarzania oprogramowania w Google. Nie jest moją ambicją streścić tę pracę w całości, a jedynie wybrać to, co wydawało mi się interesujące lub niespotykane w innych firmach. Praca pochodzi z końca stycznia 2017 roku.

  • W styczniu 2015 repozytorium Google zawierało 86 terabajtów danych. Było to w sumie miliard plików, a z tego miliada ponad 9 milionów plików to były pliki źródłowe, zawierające w sumie ponad 2 miliardy linii kodu. Dziennie do repozytorium wykonywanych było 40 tysięcy commitów, co przekładało się na historię zawierające 35 milionów commitów.
  • Programiści w Google mogą commitować do każdej części repozytorium, jeśli uznają, że mogą coś poprawić. Development większości funkcjonalności odbywa się na głównym branchu, a nie na pobocznych branchach. Każde poddrzewo w repozytorium posiada właścicieli, którzy jako jedyni mogą akceptować zmiany. Wszystkie commity są budowane automatycznie, także wtedy, gdy zmienią się zależności przechodnie. W większych zespołach istnieje rotacyjna pozycja „build copa”, który pilnuje, by wszystkie testy zawsze przechodziły na najnowszej wersji repozytorium.
  • Google posiada narzędzia, które potrafią podpowiedzieć, kogo dodać do review commitu, bazujące na historii danego programisty oraz na tym, kto ostatnio robił recenzję w wybranym obszarze, a nawet na podstawie tego, ile recenzji oczekuje w tej chwili na każdego recenzenta. Nie jest jednak jednoznacznie powiedziane, czy duża ilość oczekujących recenzji jest przesłanką do tego, by zasugerować włączenie danego recenzenta, czy przeciwnie, ktoś taki jest pomijany w sugestiach.
  • Jeśli zmiana, która została włączone do głównego brancha zawiera błąd, to praktyką w Google jest, by w code review oryginalnego commitu, który wprowadzał błąd, dać o tym znać, tak by autor zmiany i wszyscy, którzy tamten commit recenzowali, mieli świadomość tego błędu. Zdarza się też, że commity wchodzą po akceptacji tylko jednego recenzenta, a pozostali recenzenci mogą dalej dodawać swoje komentarze do commitu, ale zasugerowane przez nich zmiany wejdą w następnych commitach, wykonanych – jak rozumiem – przez tego samego programistę, który zrobił oryginalny commit. Zmiany, które zawierają szczególnie złożony kod, muszą być dodatkowo zatwierdzone przez osobę, która zdała wewnętrzne szkolenie z pisania kodu czytelnego dla innych.
  • Przed każdym deploymentem robione są testy obciążeniowe, który efektem jest raport zawierający informacje o opóźnieniu w obsłudze nadchodzących żądań i częstotliwości błędnych odpowiedzi.
  • Programiści w Google są zachęcani do programowania w 4 językach: C++, Javie, Pythonie, i Go. Ma to maksymalizować możliwość ponownego wykorzystania kodu i zapewniać możliwie bliską współpracę wszystkich programistów. Rozumiem przez to jednak, że nie jest tak, że inne języki są w Google zakazane, a jedynie, że wymieniona czwórka jest szczególnie promowana wewnątrz organizacji, i że powstają w niej najważniejsze produkty. Wymianę danych między różnymi językami programowania zapewnia projekt Protocol Buffers rozwijany przez Google. Proces budowania we wszystkich językach obsługiwany jest tym tymi samymi poleceniami, czyli np. do uruchomienia testów zawsze będzie to polecenie test.
  • Nowe wersje swoich produktów zespoły wydają często – raz w tygodniu, a nawet raz dziennie. Pozwala to wykonać więcej krótkich iteracji, szybciej reagować na bugi i feedback, i pozostawia zespoły zmotywowanymi, ponieważ na bieżąco widzą efekty swojej pracy w działaniu. Nowe zmiany są propagowane na produkcję często najpierw na małą ilość serwerów, a w przeciągu kilku dni na wszystkie, żeby zmniejszyć rozmiar potencjalnych szkód, które ostatnie zmiany mogłyby wyrządzić.
  • Duże funkcjonalności przechodzą dodatkowo review pod względem bezpieczeństwa, pod względem prawnym i pod względem tego, że są odpowiednio monitorowane na wypadek awarii. Po każdej poważnej awarii tworzone jest post-mortem, które zawiera informacje o tym, co doprowadziło do incydentu, jak przebiegał w czasie, jakich innych obszarów dotknął, co zadziałało, a co nie zadziałało, i jako można uniknąć podobnych błędów w przyszłości.
  • Większość oprogramowania w Google jest przepisywana raz na kilka lat z powodu zmieniających się wymagań i faktu, że zmienia się technologia wokół. Przyjmuje się w Google, że ilość wymagań, które narosły przez kilka lat, stanowią wystarczające uzasadnienie, żeby całkowicie wymienić kod, który nie został zoptymalizowany pod nawarstwiające się przez lata wymagania. Przy okazji przepisywania usuwa się niepotrzebną złożoność, a dodatkowo wytwarza w zespole poczucie własności względem kodu.
  • Zespoły w Google są zobligowane do wyznaczania sobie kwartalnych i rocznych celów, i do rozliczania się z nich. Ta praktyka istnieje na każdym poziomie organizacji. Oceny kwartalne i roczne służą wyłącznie do ewaluacji zespołów, a nie pojedynczych osób. Spodziewane jest, by zespół ustawił sobie jako cel wykonanie około 50% większej ilości zadań, niż jest w stanie zrealizować, stąd osiągniecie założonych celów na poziome 65% jest uznawane za wysokie.
  • W Google nie istnieje jasny proces zatwierdzania i anulowania projektów i nie ma określonego szczebla, na którym zapadają decyzję na ten temat. Zdarzają się przesunięcia wśród ludzi i zespołów, tak by nad jednym projektem pracowali ludzie w zbliżonej lokalizacji geograficznej. W takiej sytuacji programiści mogą wybrać zmianę projektu i poszukanie nowego w miejscu, w którym pracują, lub wyjazd do siedziby Google w innym kraju i podążenie za projektem, w którym są. Programiści nie zmieniają jednak projektu częściej, niż raz na rok.
  • Co oczywiste, programiści w Google są ewaluowani między innymi przez management. Co ciekawe, także menadżerowie są oceniani przez podległe im zespoły, dwa razy do roku.

Osoby zainteresowane pogłębieniem swojej wiedzy na temat praktyk wytwarzania oprogramowania w Google mogą znaleźć całą pracę w języku angielskim na tej stronie.

SOAP vs REST (Swagger)

Zaczynając pracę nad Star Trek API postanowiłem, że będzie można się z nim komunikować zarówno przez protokół SOAP, jak i za pomocą REST-u. O ile w przypadku SOAP-u wybór implementacji w Javie było oczywisty – wsdl2java opakowany w gradle’owy plugin, o tyle w przypadku REST-u nie było to już tak jednoznaczne. Nie ma zresztą jednego standardu do opisu REST-owych encji i serwisów. Ostatecznie zdecydowałem się na najpopularniejszą implementację, jaka istnieje: Swaggera, ale trzeba tutaj oddać, że RAML wydaje się równie dojrzały, co Swagger.

W tym poście opiszę plusy i minusy mojego podejścia, oraz różnice między SOAP-em i REST-em z użyciem Swaggera.

Oczywistym i od razu rzucającym się w oczy minusem tworzenia dwóch wersji modelu API jest konieczność utrzymywania dwóch wersji kodu opisującego encje i serwisy. Jeden kod w XML-u dla SOAP-u, a drugi kod w YAML-u dla REST-u. Ma to jednak plusy, ponieważ wymusza oddzielenie modelu bazodanowego od modelu API warstwą pośrednią, która musi zapewniać REST-owym i SOAP-owym endpointom jednolity model komunikacji i zapobiegnie przeciekaniu wygenerowanego kodu do warstwy repozytoriów. U mnie tę warstwę pośrednią stanowi kilka serwisów plus DTO reprezentujący kryteria wyszukiwania. Oddzielnymi mapperami, zarówno SOAP-owymi, jak i REST-owymi, zapytania są tłumaczone na wspomniany wspólny DTO, który służy do odpytania generycznego repozytorium. Następnie tymi samymi mapperami model bazodanowy jest tłumaczony na encje wygenerowane z XSD-ków i YAML-ów. Wymyśliłem to na samym początku i do tej pory rozwiązanie takie jest wystarczające. Jestem teraz w trakcie przeprowadzania małej rewolucji w modelu danych API (rozszerzanie danych zwracanych z endpointów, podział na metody do wyszukiwania encji i do pobierania pełnej encji, wraz z satelitami) i wymaga to tylko minimalnych zmian w warstwie repozytoriów.

Plusem tworzenia zarówno SOAP-owych kontraktów, jak i REST-owych specyfikacji jest możliwość dotarcia do większego grona potencjalnych odbiorców Star Trek API. Zarówno SOAP, jak i Swagger pozwalają programistom piszącym w wielu różnych językach na stosunkowo tanie wygenerowanie klientów do API. Jakość takiego generowanego kodu może być różna, niemniej stanowi jakiś punkt wyjścia. Ponadto, jeśli klient do mojego API nie będzie działać, to issue na GitHubie powstanie w issue trackerze generatora kodu, a nie w moim projekcie.

A teraz trochę o różnicach.

SOAP pozwala dokładnie opisać zarówno obiekty zapytania, jak i obiekty odpowiedzi, którymi komunikujemy się ze światem zewnętrznym. IDEA, edytor którego używam na co dzień, posiada wsparcie dla WSDL-i i XSD-ków, więc wiele błędów wyłapuje bez konieczności uruchamiania buildu, chociaż, mówiąc uczciwie, IDEA posiada po prostu wsparcie dla walidacji na podstawie XSD, a namespace WSDL-a ma zalinkowanego własnego XSD-ka. Tym niemniej, SOAP to dojrzała technologia, posiadająca stabilny standard opisu obiektów i serwisów. Dodatkowo wsdl2java generuje interfejsy, które implementujemy, aby wystawić na zewnątrz endpointy. Większość popularnych błędów, które popełniałem przy pisaniu WSDL-i i XSD-ków objawiała się sensownymi komunikatami, po których było wiadomo, co powinno zostać poprawione.

Z drugiej strony jest Swagger. Można w nim napisać encje, a z tych encji wygenerować kod javowy. Ta funkcjonalność działa. Można również wygenerować interfejsy, a nawet implementacje dla kodu serwerowego, ale po pobieżnym przejrzeniu kodu, który zostaje wygenerowany – a do wyboru jest tu kilka implementacji – postanowiłem wygenerować encje i klienty, ale napisać kod serwerowy samodzielnie, ponieważ generowały się głupoty.

W Swaggerze każdemu adresowi w API odpowiada metoda w klasie klienta. Zaskoczeniem był dla mnie sygnatury generowanych metod. Wyobraźmy sobie bowiem endpoint do wyszukiwania, do którego można przekazać dwadzieścia albo trzydzieści kryteriów. Co robi wówczas Swagger? Generuje metodę z 30 parametrami! Powodzenia w refaktorowaniu takich metod, gdy pierwsza wersja klienta pójdzie już w świat – wówczas zamiana miejscami kilku argumentów tego samego typu będzie skutkować trudnymi do wychwycenia i naprawienia błędami. Podobnie będzie z dodaniem jakiegoś argumentu w środku już istniejącej listy. Dojście do tego, co właściwie się zmieniło i jak teraz powinno się używać klienta ma potencjał dostarczyć wielu godzin rozrywki. Spodziewałbym się, że klienty swaggerowe będą tworzyły osobne obiekty, które reprezentują parametry wejściowe endpointu, ale tak nie jest. Mam w planach przykrycie swaggerowego klienta czymś właśnie takim, by inni ludzie nie musieli zgadywać, co się zmieniło w moim API.

Swagger w żaden sposób nie opracował wsparcia dla zagnieżdżonych obiektów w zapytaniach. Ostatecznie, po kilku próbach i błędach doszedłem do wniosku, że do transportu listy złożonych obiektów w zapytaniu najlepiej będzie mi na piechotę napisać serializator i deserializator. Być może przed ustabilizowaniem wersji beta wprowadzę w miejsce złożonych obiektów zwykłego JSON-a.

Swaggerowe specyfikacje można napisać zarówno w YAML-u, jak i w JSON-ie, mamy więc wybór między dwoma wymiennymi formatami. Gdy uczyłem się pisać specyfikacje, było to dla mnie tylko utrudnieniem. Tutoriale, opisy błędów i StackOverflow pełne są przykładów działającego i niedziałającego kodu zarówno w YAML-u, jak i w JSON-ie. Sam zdecydowałem się na YAML i za każdym razem, gdy widziałem kod w JSON-nie, musiałem go sobie tłumaczyć w głowie na YAML, co przychodziło mi z mozołem. Rozumiem możliwość zapisania czegoś w więcej niż jednym formacie w przypadku Spring Batcha i jego definicji XML-owych i javowych, bo tam cele i ograniczenia są nieco inne. Rozumiem też to podejście w przypadku ORM-ów, które mogą korzystać z adnotacji na encjach albo z leżących obok tych encji XML-i (jak w przypadki Doctrine w PHP, gdzie na środowisku może po prostu być wyłączony Tokenizer, a więc w rezultacie adnotacje). Ale tutaj rozumiem mniej. W Swaggerze mamy dwa szeroko rozpowszechnione, równie czytelne dla ludzi i serializujące się tak samo formaty danych. Ale może to wybór poczyniony z tego samego powodu, dla którego ja piszę na raz API REST-owe i SOAP-owe – żeby więcej ludzi skorzystało z rozwiązania.

Kolejnym minusem Swaggera jest wyjątkowo rozluźnione traktowanie błędów. Przykładowo, gdy specyfikując typ pola w encji wpisałem strin zamiast string, pole po prostu się nie wygenerowało, ale nie było żadnego komunikatu o błędzie, a na pewno nie taki, za którym idzie przerwanie buildu. Podobnie w przypadku, gdy pomyli się nazwy encji linkowanych w specyfikacji endpointów. Swagger po prostu wygeneruje kod javowy, który się nie skompiluje, bo klient API będzie wskazywał na nieistniejącą klasę.

Rozpisałem się o minusach Swaggera, ale to nie jest tak, że nie da się go używać. Da się. Używam. Ale przy następnej okazji spróbuję RAML-a.

Zaczynałem ten eksperyment bez żadnego biasu. Jedyną rzeczą, którą od początku się spodziewałem, było to, że barokowe XML-e będą zajmowały więcej miejsca, niż YAML-owe specyfikacje. Ale i to nie do końca się sprawdziło. Jeśli chodzi o miejsce zajmowane przez YAML-e w pionie, jest go nawet kilka procent więcej. Jeśli chodzi o miejsce zajmowane w poziomie, jest go oczywiście mniej.

Nigdy nie miałem okazji budować API, które udostępnia te same operacje za pomocą REST-u i SOAP-u. Po napisaniu kilkunastu takich samych endpointów REST-owych i SOAP-owych nabrałem przekonania, że REST z użyciem Swaggera nie ma żadnych przewag nad SOAP-em. Natomiast przewagi SOAP-u na Swaggerem to, między innymi, możliwość wysyłania złożonych obiektów w zapytaniach, sensowniejszy generowany kod, lepsza walidacja, lepsze wsparcie w IDE i generalnie dojrzalszy i lepiej ustabilizowany ekosystem. Ale Swagger jest jak najbardziej używalny i na pewno się do niego nie zniechęcam, natomiast mam już teraz świadomość jego ograniczeń.

Jak szybko załadować sobie kontekst przed pracą

Niektórzy słyszeli pewnie o zjawisku, które nazywa się Cognitive Switching Penalty. W uproszczeniu chodzi w nim o to, że w momencie, gdy przełączamy się miedzy zadaniami, tracimy czas i skupienie, a rośnie nasz poziom stresu i frustracja (ciekawy wątek w temacie). Widać to dobrze na przykładzie liderów zespołów, którzy starają się też programować, a którym ciągle ktoś przerywa, i nie są oni w stanie skończyć prostych zadań, które zaplanowali sobie na najbliższy czas.

Napotykam na zbliżony problem zarówno wówczas, gdy zaczynam rano pracę, jak i wtedy, gdy siadam do programowania po południu. Wówczas załadowanie kontekstu z poprzedniego dnia może mi zająć kilka dobrych minut. Odkryłem sposób, który skraca czas ładowania kontekstu z poprzedniego dnia. Od razu zaznaczam, że najpewniej nie odkryłem go jako pierwszy, niemniej zrobiłem to samodzielnie.

Sposób jest następujący: zarówno w biurze, jak i w domu kończę pracę, zostawiając sobie nieprzechodzący test jednostkowy. Wówczas, gdy siadam po przerwie do kodu, od razu mam proste zadanie, które muszę zrobić: muszę zaimplementować kod, aby test zaczął przechodzić. Zaimplementowanie kodu, by test się zazielenił, może wtedy zająć trzy minut, może zająć pięć minut, i to wystarczy, bo po takim czasie mam już odpowiedni flow, który pozwala mi płynnie przechodzić do kolejnych zadań.

Podobnie skuteczne w ładowanie kontekstu z poprzedniego dnia jest zostawienie niedokończonego testu do już zaimplementowanego kodu produkcyjnego. (Kto zawsze pisze testy przed kodem, niech pierwszy rzuci kamień.)

Ta metoda nie sprawdza się tak dobrze w przypadku prac utrzymaniowych, jak w przypadku implementacji nowych funkcjonalności. Minusem jest też to, że kto w ogóle nie pisze testów, nie będzie w stanie jej zastosować.

Nie stosuję tego sposobu zbyt restrykcyjnie, ale gdy wiem, że do końca dnia pracy lub programowania w domu zostało mi około kwadransa, zaczynam planować, gdzie w obszarze, w którym właśnie pracuję, mógłby powstać nieprzechodzący test, od którego mógłbym zacząć następnego dnia.

Zasilanie danymi: procesory

Aby stworzenie Star Trek API w zaplanowanym kształcie miało sens, musiało znaleźć się źródło danych, które byłoby relatywnie kompletne i które umożliwiałoby dostęp do danych przez sensowne i legalne API. Tym źródłem jest Wikia, wraz z dwoma hostowanymi tam wiki – Memory Alpha i Memory Beta.

Wszystkie wiki na Wikii działają na oprogramowaniu MediaWiki, rozwijanym od wielu lat na potrzeby Wikipedii. MediaWiki udostępnia własne API – niezbyt ustandaryzowane, ani SOAP-owe, ani REST-owe. Na Bitbuckecie istnieje projekt Bliki engine, javowy klient, który, chociaż nie idealny, oszczędza dużo pracy przy kwerendowaniu API stron opartych na MediaWiki.

Samo zasilenie składa się z trzech zasadniczych kroków. Readery i writery opisałem w poście o Spring Batchu. W tym wpisie skupię się na przybliżeniu szczegółów implementacji procesorów.

Po pierwsze, trzeba zbudować zbiór stron, z których zostanie zasilony dany model. Wymaga to ręcznego przejrzenia kategorii, w których pogrupowane są strony na wiki. Przykładowo, aby zbudować listę stron postaci fikcyjnych, które zostaną przeparsowane w następnym kroku, należy pobrać strony z czterech kategorii, z uwzględnieniem stron w kategoriach, które znajdują się we wskazanej czwórce. Przykładowo, kategorią, którą trzeba pobrać wraz z podkategoriami, jest kategoria Individuals. Trzeba uważać, by przy przechodzeniu do podkategorii odfiltrować te, z których strony były już pobierane, inaczej skończyłoby się to nieskończoną pętlą. Zdarza się bowiem tak, że jedna z podkategorii jest w więcej niż jednej kategorii wyższego poziomu.

Następnie wszystkie strony, które do tej pory były reprezentowane jako nagłówki, zostają pobrane wraz z całą treścią, kategoriami, i sparsowanymi szablonami. Wikia nie udostępnia sparsowanych szablonów, ponieważ jej wersja MediaWiki nie daje tej możliwości, dlatego trzeba pobraną z Wikii stronę przepuścić przez dodatkowy parser, np. przez ten publicznie dostępny na Wikipedii, chociaż ja używam postawionego lokalnie MediaWiki z powodów wydajnościowych. Zanim strony będą procesowane, następuje odfiltrowanie tych, które nie powinny się tu znaleźć. W tej grupie zawierają się strony zbiorcze i przeglądowe, które, chociaż znajdują się w pożądanych kategoriach, nie reprezentują postaci fikcyjnych, a, przykładowo, opisują jakąś ich grupę w egzotycznym kontekście, np. grupują postacie, które były tylko złudzeniami innych postaci, lub grupują reprezentantów jakiejś rasy, którzy nie byli znani z imienia.

Następnie dla większości encji, które mają być stworzone, poszukiwane są szablony znany z Wikipedii jako infoboksy – to te tabelki po prawej stronie na Wikipedii, z wypisanymi w nich najważniejszymi informacjami. Te szablony są, obok kategorii, w których znajduje się strona, głównymi źródłem danych w modelu. Infoboksy z Memory Alpha są w Star Trek API reprezentowane przez osobne obiekty względem encji bazodanowych, i w jeszcze kolejnych obiektach trzymany jest metamodel dla szablonów, czyli lista pól, które w nich występują, przykładowo, data urodzenia, płeć lub rasa, do której przynależy postać. Chociaż oddzielenie szablonów od encji może wydawać się nadmiarowe, posługiwanie się tą samą encją dla dwóch różnych zadań, wydawało mi się z kolei niezgodne z dobrymi praktykami. Z czasem okazało się też słuszne, bo czasami jednak procesor wypluwający z siebie encję reprezentującą szablon jest używany do czego innego, niż stworzenie na jego podstawie encji bazodanowej.

Procesory mogą być całkiem proste, i ograniczać się do jednej klasy bez zależności, jak w przypadku tego procesora, jak i całkiem skomplikowane, jak ten, który i tak tylko deleguje parsowanie kolejnych pól szablonu do swoich zależności. Dane zawarte w szablonach mogą i często są niekompletne, dlatego też model w Star Trek API ma niewiele obowiązkowych kolumn. Zwłaszcza w przypadku informacji ze świata fikcyjnego ciężko o komplet informacji – jeżeli postać epizodyczna nie miała podanej rasy, prawdopodobnie nigdy już nie będzie jej miała, chyba że wystąpi jeszcze w przyszłości w jakimś komiksie.

Zasadniczo w Star Trek API istnieją dwa typy procesorów. Jednym typem procesorów są te ze Spring Batcha o sygnaturze O process(I item) throws Exception, a drugi typ to – napisany już przeze mnie – ItemEnrichingProcessor o sygnaturze void enrich(I enrichablePair) throws Exception. Mój procesor do transportu obiektów używa EnrichablePair, czyli obiektu, który przechowuje zarówno wejściowy, jak i wyjściowy obiekt – a więc ten, który ma zostać wzbogacony. Ten drugi procesor jest używany wówczas, gdy zachodzi konieczność ustawienia więcej niż jednego pola w obiekcie reprezentującym szablon, lub zaczerpnięcia danych z więcej niż jednego pola. O bojach z danymi, które przechodzą przez procesory, będę pisał więcej w kolejnych tygodniach.

Spring Batch

Zaczynając pracę nad Star Trek API poszukiwałem narzędzia, które pozwoliłoby na elastyczną konfigurację wieloetapowego procesu zasilenia bazy danych danymi z wielu źródeł. Nie znałem takiego narzędzia z pierwszej ręki, ale chcąc uniknąć eksperymentów i konieczności wycofywania się z raz podjętej decyzji, postawiłem na najpopularniejsze dostępne rozwiązanie – Spring Batch, i jak dotąd, sprawdza się on bardzo dobrze, z wyjątkiem dwóch niedogodności, o których napiszę na koniec.

Spring Batch dostarcza ogólny szkielet, który wypełniamy naszą logiką biznesową. Na najwyższym poziomie w Spring Batchu znajdują się joby, których możemy mieć wiele i które mogą być wykonywane sekwencyjnie lub współbieżnie. W każdym jobie znajduje się wiele kroków, które również mogą być wykonywane sekwencyjnie lub współbieżnie. Kroki mogą być restartowalne, wykonywane raz, lub mieć limit wykonań. W moim przypadku pożądanym było, by każdy krok wykonywał się raz, ponieważ przyjąłem założenie, że baza będzie każdorazowo zasilana od nowa. Dopiero w przyszłości zaimplementuję zasilanie przyrostowe, które będzie ograniczone do tych danych źródłowych, które zmieniły się od ostatniego zasilenia.

Krok w Spring Batchu składa się z readera, procesora i writera. Reader, zależnie od implementacji, ładuje wszystkie dane przez rozpoczęciem kroku, lub doładowuje je przed wywołaniem procesora. Procesor implementuje interfejs ItemProcessor<I, O>, gdzie I to typ obiektu, od którego zaczyna się przetwarzanie, a O to typ zwracanego obiektu. Procesor ma tylko jedną metodę o sygtaturze O process(I item). Procesory mogą być springowymi beanami i dzięki temu logika wewnątrz pojedynczego procesora może być dowolnie rozbudowana. Ostatnim elementem kroku jest writer, który zapisuje wynikowe obiekty. Mogą być one zapisane gdziekolwiek, np. na dysk lub do jakiegoś cache’u, do użycia w jednym z kolejnych kroków, ale w przypadku startrekowego API naturalnym jest, by przetworzone encje trafiały do bazy danych.

Pojedynczy krok w Star Trek API najczęściej wiąże się z zasileniem pojedynczej tabelki w bazie danych. Aby stworzyć krok, najpierw trzeba wyznaczyć grupę kategorii, w których znajdują się strony mogące się przełożyć na wiersz w tabeli. Przykładowo, taką kategorią, od której może zacząć się krok, są Companies, czyli firmy, które pracowały nad Star Trekiem. Następnie do pamięci ładowane są nagłowki stron z danej kategorii. Nagłówki zawierają ID strony, jej nazwę, oraz źródło – Memory Alpha lub Memory Beta. Na tym kończy się reader. Następnie, już w procesorze, na podstawie takich nagłówków jest pobierana cała treść strony, i wóczas wykonywane jest przetworzenie w sposób właściwy dla danej encji, które kończy się przemapowaniem encji reprezentującej szablon z wiki na encje bazodanową. Szablony to infoboxy, które na pewno każdy widział na Wikipedii po prawej stronie wielu stron. Na koniec mamy zapis, które obejmuje dodatkowo odfiltrowanie duplikatów. Duplikaty biorą się z faktu, że na wiki istnieją przekierowania, co powoduje, że dwa różne nagłówki stron mogą wskazywać na tę samą stronę.

Kolejne kroki przetwarzające encje są konfigurowalne przez javowe propertisy. To już funkcjonalność, którą dopisałem sam. Każdy krok można uruchomić oddzielnie, lub można uruchomić kilka wybranych kroków, a inne wyłączyć. Kroki nie posiadają między sobą zależności i można je wykonywać w dowolnej kolejności i jako dowolny podzbiór. Zależności istnieją tylko między encjami bazodanowymi. Jeśli, przykładowo, chcę przetworzyć komiksy bez przetworzenia wpierw firm, wówczas wszystkie komiksy będą miały wydawcę ustawionego na null, ale poza tym zostaną z powodzeniem zapisane.

Żeby już nie duplikować kodu na blogu, przykład tego, jak wygląda reader, processor i writer można zobaczyć tutaj.

A teraz o dwóch niedogodnościach.

Jedną niedogodnością, jaką napotkałem, wynikającą być może z początkowej nieznajomości Spring Batcha, była konieczność dwukrotnej zmiany formatu konfiguracji. Najpierw napisałem konfigurację javową, ale po jakimś czasie została ona wymieniona na konfigurację XML-ową z powodu trudności w testowaniu Javy, oraz dlatego, że więszkość tutoriali, na które natrafiałem, używała notacji XML-owej. Trzy tygodnie później doszedłem do wniosku, że XML-owa konfiguracja nie daje się łatwo konfigurować za pomocą propertisów, i ponownie wymieniłem ją na javową, tym razem przykładając się do napisania pełnych testów.

Drugą niedogodnością, wynikającą być może z chęci stworzenia zbyt wysokopoziomowego interfejsu przez autorów Spring Batcha, jest niemożność łatwego dostania się do repozytoriów, które odpowiadają za operacje na jobach i stepach. Potrzebowałem tego, żeby nie ładować danych do już zakończonych kroków, bo czasami może to trwać kilka minut, a ładowanie danych odbywa się w momencie tworzenia springowych beanów, czyli efektywnie w trakcie startu aplikacji. Po dłuższym grzebaniu w kodzie doszedłem do rozwiązania, które na pewno nie spodobało by się autorom Spring Batcha. Można je obejrzeć tutaj. Sprowadza się ono do odpakowania klasy SimpleJobRepository ze springowego proxy, a następnie do wystawienia kilku jego prywatnych pól, wydobytych refleksją, jako beany. Brzydkie rozwiązanie, ale działa.

Udział w DSP’2017

Postanowiłem wziąć udział w konkursie Daj się poznać. Dało mi to pretekst do otworzenia tego bloga.

Do konkursu zgłosiłem się o z projektem Star Trek API, który rozwijam na GitHubie od października 2016 r.

Star Trek API to próba uporządkowania danych na temat uniwersum Star Treka i ujęcia ich w model relacyjny, z wykorzystaniem swobodnie dostępnych w internecie źródeł. W kolejnym kroku celem jest wystawienie tych danych jako otwarte API, dostępne online. Główna faza developmentu zaczęła się, zanim zaczął się konkurs, i potrwa jeszcze po jego zakończeniu.

Głównym źródłem danych dla API jest w tej chwili Memory Alpha, hostowane na Wikii wiki dotyczące kanonicznego uniwersum Star Treka, i – w znacznie mniejszym stopniu – Memory Beta, inne wiki dotyczące uniwersum Star Treka, które gromadzi informacje o fanowskich produkcjach.

Stos technologiczny w startrekowym API to Java i Spring do kodu produkcyjnego, oraz Groovy i Spock do testów. To stos, którego używam na co dzień w pracy, a chciałem, by projekt został napisany możliwie dobrze i bez eksperymentów z technologiami. To także stos na tyle popularny, że być może w przyszłości znajdą się chętni, by ten projekt ze mną rozwijać.

Inspiracją do pójścia w kierunku stworzenia API w uniwersum Star Treka były 2 projekty, na które natknąłem się w drugiej połowie 2016 roku w sieci: SWAPI oraz Pokéapi, czyli odpowiedni API dotyczące tematyki uniwersum Star Wars i API dotyczące Pokémonów. Star Trek był franczyzą, która zawsze mnie interesowała, i upewniwszy się, że żadnego podobnego projektu nie ma jeszcze w sieci, postanowiłem rozpocząć swój.

API ma docelowo zawierać zarówno dane z prawdziwego świata, dotyczące uniwersum Star Treka, czyli aktorów, seriale, firmy pracujące przy produkcji, komisy i książki, jak i szereg fikcyjnych bytów fikcyjnych: postacie, obiekty astronomiczne, rasy występujące w serialu, statki kosmiczne czy jedzenie. W tej chwili zaplanowanych jest ok. 35 encji, ale liczba ta może być ostatecznie zarówno niższa, np. przez połączenie kilku typów książek w jedną encję, jak i większa, w związku z rozszerzaniem o kolejne źródła danych.

W momencie pisania tego postu jestem w ok. 1/3 drogi do ukończenia wersji beta, którą będzie można bez obaw udostępnić online. Na stronie Work progress na wiki można śledzić postępy prac, mierzone ilością ukończonych encji.

Kolejne posty planuję publikować, przez czas trwania konkursu, zawsze w środy i w soboty.