@Unroll w Spocku, przykład zaawansowany

Spock jest frameworkiem do testów, którego popularność w świecie JVM-a jest nie do podważenia.

W internecie można znaleźć wiele tutoriali, które wprowadzają w podstawy adnotacji @Unroll. Ogólnie chodzi w niej o to, że mamy możliwość uruchomienia tego samego kodu z wieloma zestawami danych. Najprostszy przykład użycia @Unroll wygląda tak:

Interesowało mnie, ile jeszcze można wycisnąć z tej adnotacji, i o tym będzie ten post.

Wymagania

Przy pisaniu procesorów, które tworzą encje takie, jakie Localization, potrzebowałem przetestować sytuację, gdy do procesora wchodzi encja reprezentująca stronę z Memory Alpha, posiadająca jakiś zestaw kategorii. Na podstawie obecności tych kategorii były ustawiane konkretne booleanowe flagi w encji wyjściowej, w tym przypadku właśnie Localization. Gdybym chciał to zapisać w kilkunastu czy kilkudziesięciu testach, powtórzyłbym bardzo dużo kodu.

Jednocześnie chciałem sprawdzić w każdej iteracji więcej, niż jedną daną wyjściową, ponieważ obok flagi, która w sytuacji obecności danej kategorii miała być ustawiona na prawdę, chciałem też sprawdzać liczbę nienullowych pól w całej encji wyjściowej.

Trzecim wymaganiem było, by nazwa flagi była przechowywana jako string. To także okazało się możliwe z @Unrollem. Chociaż zapewne to głównie zasługa Grooviego, który pozwala na dostęp do obiektów z użyciem tej samej składni, która umożliwia dostęp do map.

Ostatnim wymaganiem było, by zapisać w metodzie testowej interakcje, najlepiej z wyspecyfikowaną ich ilością. To też się prawie udało.

Implementacja

Ostateczna implementacja wygląda tam:

Opis implemenacji

W bloku given zostały zapisane wszystkie wymagane w każdej iteracji interakcje. Można by też po prostu dostarczyć do metody testowej już zaprogramowane stuby, które miałyby te same interakcje.

W bloku expect pobieramy lokalizację z procesora, a następnie, z użyciem nazwy flagi, którą też mamy w datasecie, porównujemy wartość flagi z kolejną wartością zapisaną w datasecie. Ostatecznie refleksją sprawdzamy ilość nienullowych pól w encji reprezentującej lokalizację.

W bloku where znajdują się dane. Tutaj największą niespodzianką jest to, że można w ramach bloków konstruować obiekty i odwoływać się o innych metod. Patrząc na przykłady w sieci, nie jest to wcale oczywiste.

Ograniczenia

Nie znalazłem sposobu, by wyspecyfikować ilość interakcji w ramach jednej metody testowej z @Unrollem. Interakcji nie można umieszczać w blokach given i expect, a z kolei blok then nie może współegzystować z blokiem expect. Jeśli więc chcemy sobie zaprogramować interakcje, możemy to zrobić tylko w bloku given, ale bez zapisywania przy nich ilości.

Podsumowanie

Umiejętnie użyty @Unroll pozwala napisać dowolny test, z wyjątkiem takiego, który zlicza ilość interakcji.

Kiedy używać @Unrolla, a kiedy jest on overkillem? Myślę, że dobrą granicą są cztery bardzo podobne metody testowe. Jeśli tyle mamy, należałoby je połączyć w jedną metodą z @Unrollem. Ale zgodnie z duchem reguły DRY, nawet 2 podobne metody testowe to nie byłoby za dużo, by zacząć używać @Unrolla.

A co, jeśli mimo wszystko chcemy zliczać interakcje i mieć data tables? Moje doświadczenie pokazuje, że lepiej rozdzielać kod, który głównie operuje na interakcjach z zależnościami, od kodu, który głównie operuje na danych. Jeśli powstaną dwie osobne klasy, albo przynajmniej dwie osobne publiczne metody, z których jedna operuje głównie na zależnościach klasy, a druga głównie przetwarza dane, problem z brakiem zliczania interakcji przy użyciu @Unrolla powinien zostać zminimalizowany.

Throttling API w Springu

Publicznie dostępne API, takie jak Star Trek API, w miarę, jak zyskują popularność, muszą poradzić sobie ze zwiększającym się ruchem. Jedną ze strategii radzenia sobie ze zbyt wieloma zapytaniami jest nałożenie limitu na klienty. Można to zrobić na kilka sposobów, np. implementując limit godzinny lub dobowy dla danego adresu IP, lub odnawialne, płatne lub odświeżane cyklicznie, limity dla kluczy API.

Istniejące implementacje

Spodziewałem się, że ten problem został już rozwiązany i wystarczy dorzucić do projektu zależność, która zajęłaby się throttlingiem. Tak jednak nie jest. Istnieje co prawda Apache CXF Throttling Feature, ale analizując kod doszedłem do wniosku, że nie nadaje się on do throttlingu w sytuacji, gdy mamy wiele klientów. Nadaje się jedynie do throttlingu per zasób, a to mnie nie zadowala. W internecie można znaleźć też posty polecające RateLimiter z Guavy, ale tutaj z kolei filozofia jest taka, że limitujemy dostęp do zasobu, a wątek, który chce zająć zasób, ma czekać, jeśli zasób jest już zajęty. To nie wpisuje się w moją wizję.

Założenia

Postanowiłem zaimplementować omawianą funkcjonalność samodzielnie. Na pierwszy ogień poszedł throttling na podstawie adresu IP. Założenia były proste. Adresy IP, które uzyskują dostęp do API, będą zapisywane w bazie. Pierwszy zapis adresu IP ustawi jego maksymalny limit zapytań na 100, a ilość pozostałych zapytań na 99. O każdej pełnej godzinie limity są odświeżane i każdy adres IP może znów wykonać 100 zapytań. Co godzinę, 30 minut po pełnej godzinie, adresy które nie wykonywały zapytań dłużej, niż dzień, są usuwane.

Model

Postanowiłem nie zamykać się na możliwość, że w przyszłości zaimplementuję także throttling na podstawie kluczy API, a więc schemat bazy danych przygotowałem pod obie te ewentualności. W ten sposób powstał model oraz towarzyszące mu repozytorium z niezbędnymi operacjami.

Interceptory

Skoro było już skąd pobrać i gdzie zapisać informację o tym, czy dany adres IP może dalej wysyłać zapytania do API i otrzymywać poprawne odpowiedzi, trzeba było zaimplementować interceptory, które pozwoliłyby odpowiednio wcześnie zdecydować o tym, że przetrwarzania żądania powinno zostać anulowane. Nie chodzi wszak o to, żeby zapytać bazę, a na koniec zamiast poprawnej odpowiedzi pokazać komunikat o błędzie.

Na początek trzeba było stworzyć interfejs ApiThrottlingInterceptor. Bean implementujący interfejs zostaje zarejestrowany w trakcie tworzenia CXF-owego serwera, oraz we wszysktich endpointach SOAP-owych, w ich wspólnej metodzie wytwórczej. Interfejs będzie implementowany przez dwie klasy, zależnie od tego, czy jest włączony springowy profil odpowiedzialny za throttling, czy nie. Wówczas, gdy profil nie jest obecny, jako bean zostanie zarejestrowana klasa, która nic nie robi. Jeśli profil jest obecny, zostanie zarejestrowana klasa, która deleguje nadchodzące zapytanie do fasady.

Proces walidacji

Zadaniem fasady jest przekazanie nadchodzącego zapytania do walidatora i, jeśli walidator nie pozwoli, by zapytanie było dalej procesowane, rzucenie wyjątku, inego dla REST-owych, a innego dla SOAP-owych zapytań.

Walidator ma na celu pobranie adresu IP z zapytania i sprawdzenie, czy ten adres IP może jeszcze wykonywać zapytania. Jeśli tak, zwraca DTO z flagą throttle ustawioną na fałsz, i pustymi komunikatem o błędzie. Jeśli adres IP nie może już wykonywać zapytać, zwracany jest DTO z flagą throttle ustawioną na prawdę, oraz powodem throttlingu.

Komunikaty o błędach

Jeśli chodzi o SOAP-owy wyjątek, rzucenei SoapFault-a wystarczy, by CXF zamienił go na komunikat w spodziewanym formacie. W przypadku REST-owego błędu trzeba była napisać customowy mapper, który przemapowuje REST-owy wyjątek na format zdefiniowany w swaggerowym kontrakcie.

Procesy cykliczne

Na koniec trzeba było stworzyć dwa procesy cykliczne. Jeden do odświeżania limitów IP, a drugi do kasowania starych wpisów z adresami IP. Oba jako jedyną zależność mają repozytium encji Throttle, i po prostu wykonują na nim wymagane operacje.

Podsumowanie

I to właściwie wszystko. Jedyną podstępną rzeczą, na którą trzeba uważać w implementacji takiej jak ta, to by dekrementować ilość pozostałych zapytań za pomocą SQL, a nie Javy. Może się bowiem zdarzyć, że pobierzemy dla kogoś limit i zechcemy go w następnej operacji zapisać pomniejszony o 1, a w międzyczasie proces cykliczny ponownie ustawi limit na 100. Czyli trzeba zrobić tak.

Myślę, że zaimplementuję w przyszłości także throttling na podstawie kluczy API. Nie robię tego w tej chwili, bo w przeciwieństwie do limitowania dostępu do API na podstawie adresu IP, limitowanie na podstawie kluczy wymaga, by powstał jakoś frontend, do którego można się zalogować, najlepiej GitHubem, Facebookiem i Googlem. Wymagane byłoby tu też, aby każdy zalogowany użytkownik mógł swoimi kluczami zarządzać, czyli przynajmniej tworzyć nowe, inwalidować stare, a być może też requestować większe limity lub je dokupować. To praca, której nie chcę na razie wykonywać, i wolę skupić się na skończeniu samego API.

Statystyki repozytoriów projektów zgłoszonych do DSP

Jeśli chcesz przejść bezpośrednio do statystyk, kliknij tutaj.

Motywacja

Istnieje już strona zbiorcza z postami, gdzie możemy się dowiedzieć, kto i kiedy opublikował ostatni post. Po uruchomieniu w konsoli polecenia $(".panel-heading.text-center.blog-title").length dowiemy się dodatkowo, ile osób opublikowało post w ciągu ostatniego tygodnia. To ciekawa statystyka, ale brakowało mi informacji o tym, jak tak naprawdę idzie pozostałym uczestnikom, jeśli chodzi o projekty, a nie blogi, i – nie ukrywam – jak wypadam na ich tle.

Rezultat

Z początkiem marca, w przerwach od pisania Star Trek API robiłem jeszcze jeden projekt. Jego cel był prosty: korzystając ze zbiorczej strony z tegorocznymi uczestnikami, zebrać linki do wszystkich repozytoriów, ściągnąć te repozytoria, zgromadzić na ich temat statystyki, a następnie opublikować w formie interaktywnego grida. Od kilku dni aplikacja, która to wszystko robi, jest już napisana. Codziennie wieczorem odpalam ją, żeby zebrała statystyki aktualne na poprzedni dzień. Od kilku dni niczego już nie poprawiam, więc myślę, że to dobry moment, by opublikować wyniki. Można je zobaczyć tutaj.

Aktualizacje

Aktualizacje statystyk będę przeprowadzał przez cały czas trwania konkursu. Codziennie wieczorem, a w weekendy w ciągu dnia, będą pojawiały się statystyki aktualne na poprzedni dzień.

Stos technologiczny

Aplikację do zbierania statystyk napisałem w tym samym stosie, w którym piszę Star Trek API: kod produkcyjny powstał w Javie, a testy w Groovym. Aplikacja nie ma bazy danych, i po prostu raz dziennie generuje JSON-a ze wszystkimi statystykami, który jest potem ładowany do ag-Grida. Intensywnie wykorzystywanymi zależnościami backendowymi są też jsoup (do parsowania HTML-a) i EGit (obiektowy wrapper wokół Gita).

Jak zbierane są statystyki

Statystyki zbierane są dla okresu od 1 marca do dnia podanego na stronie statystyk. Przykładowo, dziś, 8 kwietnia przed południem uruchomiłem zasilenie, które zebrało statystyki od 1 marca do 7 kwietnia. Od strony technicznej wygląda to tak, że dla każdego commitu generowany jest diff z poprzednim commitem i zliczana jest ilość zmienionych, dodanych i usuniętych linii. Zliczana jest też ilość commitów. W większości kategorii, poza ilością commitów na dzień, liczby są zokrąglane do integerów, więc mogą istnieć jakieś minimalne rozjazdy (np. ktoś dodaje dziennie 220 linii, zmienia 293 linii, ale usuwa 72). Wierzę, że takie zaokrąglenia nie wpływają na ogólny obraz projektów.

Obserwacje

Oto kilka obserwacji, które poczyniłem do tej pory, przeglądając zgromadzone statystyki:

  • Wiele osób podało niepoprawne linki do repozytoriów. Były to między innymi linki do profili użytkowników na GitHubie.
  • Wiele repozytoriów do tej pory jest pusta.
  • Niektóre z osób, ktore zgłosiły projekty do DSP, miało już napisany wcześniej jakiś kod, ale nie był on wersjonowany, i initial commit był wykonywany na potrzeby DSP.
  • Są osoby, które commitują do repozytium rzeczy, które nie powinny się tam znaleźć, tzn. te, który powinny być sciągnięte jakimś build toolem, w efekcie w statystykach wygląda to tak, jakby commitowały po kilka tysięcy linii dziennie.
  • Pierwsza osoba, jeśli chodzi o ilość wykonanych commitów, pisze 10 razy mniej linii dziennie, niż druga osoba w tej kategorii.
  • Tylko 49 osób wykonuje więcej, niż jeden commit dziennie.
  • Wśród osób, które zrobiły jakieś commity, 18 osób zacommitowało poniżej 2 linii dziennie.
  • Przyszłość

    Zachęcam do samodzielnego oglądania statystyk i do wyciągania wniosków. Jeśli ktoś chciałby się ze mną skontaktować w sprawie statystyk, bo ma na przykład pomysł na dodatkową funkcjonalność, albo nie chce być na liście, może to zrobić mailem, na Slacku lub w komentarzach.

Brakujący feature JPA

Ten post będzie jedynie narzekaniem na niedoskonałość technologii, z którą pracuję nad projektem zgłoszonym do DSP.

Problem

W trakcie tworzenia Star Trek API muszę modelować relacje wiele do wielu w znacznej ilości. Często jest tak, że dwie encje mają między sobą kilka relacji wiele do wielu. Relacje te oznaczają oczywiście co innego. Np. mam filmy, a w nich relacje do ludzi pracujących nad Star Trekiem w różnych rolach. Oddzielnie są producenci, oddzielnie autorzy scenariusz, i tak dalej. W kodzie wygląda to na przykład tak:

Jak widać, relacja między encją Movie i encją Staff występuje więcej niż raz. Trzeba więc utworzyć dwie tabelki pośrednie, movies_screenplay_authors i movies_story_authors, dla obu tych relacji. Jeśli tabelek jest więcej, to trzeba utworzyć więcej relacji. W efekcie w w bazie mam tabelkę dla filmów i relacje filmów z innymi encjami:

movie
movies_characters
movies_directors
movies_performers
movies_producers
movies_screenplay_authors
movies_staff
movies_stand_in_performers
movies_story_authors
movies_stunt_performers
movies_writers

Z czasem coraz ciężej przeglądać mi bazę w SQL Developerze. Tabelek z danymi mam w tej chwili niespełna 20, a tabelek z relacjami ponad 50.

Rozwiązanie, które nie istnieje

To, czego brakuje w obecnej sytuacji, to możliwość użycia jednej tabelki pośredniej dla kilku relacji. Wydaje mi się, że jest to niemożliwe w JPA 2.1. Wyobrażam sobie, że od strony kodu wyglądać mogłoby to tak:

W ten sposób nasza implementacja JPA, np. Hibernate, wiedziałaby, że aby utworzyć relację między filmem i scenarzystą, musi dodać wpis do tabelki movies_staff, dodatkowo ustawiając wartość kolumny staff_type na "STORY_AUTHOR". Abstrahuję tutaj od potencjalnych problemów wydajnościowych. Star Trek API, z maksymalnie kilkoma tysiącami encji w każdej z tabelek, z pewnością nie cierpiałoby z powodu problemów z wydajnością. Od strony SQL-a takie rozwiązanie również nie wydaje mi się niemożliwe.

Any help?

Byłoby wdzięczny za sugestie, czy da się opisywany problem jakoś rozwiązać. Może istnieją jakieś przeciwskazania do implentacji tego rodzaju funkcjonalności? A może istnieje ona w Hibernate, ale nie w JPA?

Wczoraj zadałem podobne pytanie na StackOverflow. Okazało się, że to, o czym piszę, rzeczywiście nie jst możliwe w JPA, ale jest możliwe do osiągnięcia z użyciem DataNucleus, frameworka, który również implementuje JPA.

Czy zmienię implementację JPA dla jednej funkcjonalności? Pewnie nie, ale dobrze mieć opcje.

Projekty po godzinach

Zasadniczo robię dwa typy projektów po godzinach. Te, które mają mnie czegoś nauczyć i te, które mają przynieść jakąś wartość innym. Projektem drugiego typu jest Star Trek API, nad którym pracuję w ramach tegorocznego konkursu DSP.

Zrobiłem przez ostatnie trzy lata kilka projektów na GitHubie i dwa poza nim – nieudane POC-i. Historia większości z nich była podobna – zaczynałem je robić, po czym z różnych powodów je porzucałem.

Moje projekty

  • Mój pierwszy wrzucony na GitHuba projekt, a jednocześnie jedyny w tej chwili ukończony, to simone, javascriptowy manager okien. Zyskał nawet kilka gwiazdek na GitHubie i parę osób wykonało forki. Na szczęście ten projekt nie wymaga ode mnie utrzymania, być może dlatego, że nikt go na poważnie nie używa.
  • Kolejny był qunit-desktop-notifications, plugin do QUnita, który prawie ukończyłem, ale straciłem nim zainteresowanie, ponieważ skończyłem w pracy projekt, w którym QUnit był w użyciu. Ten projekt, w przeciwieństwie do simone, był już budowany na Travisie i miał raporty z pokrycia kodów testami trzymane na Coveralls. Jego zakres był tak wąski, że nie sądzę, żeby kiedykolwiek komuś się przydał.
  • Następnie był ComicCMS2, próba stworzenia następcy dla ComicCMS. Dodatkowo realizowanym celem było zaznajomienie się z technologiami, których miałem używać w nowej pracy. Były to głównie Zend 2, Doctrine, PHPUnit i Angular. Porzuciłem ten projekt, ponieważ zmieniałem technologię na Javę.
  • Aby zmienić technologię na Javę, rozpocząłem projekt Carmen. Miał to być projekt, który pozwoli na zbieranie statystyk na temat wszystkich repozytoriów na GitHubie, i, z czasem, także statystyk z innych źródeł (Bitbucket, GitLab, itd.). Zawiesiłem go na czas robienia Star Trek API, ale widzę jeszcze jakąś szansę, że do niego wrócę, w przeciwieństwie do wcześniejszych projektów.
  • Były też 2 projekty, które nie wyszły poza fazę proof of concept. Pierwszym z nich była pisana w Rubym aplikacja, która miała za zadanie porównywać pliki PSD. Została porzucona, ponieważ bibliteka do parsowania PSD, psd.rb, była zabugowana w stopniu, który uniemożliwiał pracę nawet z prostymi plikami.
  • Drugim projektem, który nie wyszedł poza fazę POC, była próba przepisania części Gita na PHP, w celu umożliwienia wrzucania kodu na serwery tam, gdzie nie było Gita – a pracowałem w PHP z klientami, którzy mieli naprawdę archaiczne i ubogie serwery, i żadnej woli upgrage’u. Porzuciłem projekt, ponieważ przepisywanie kodu z C i implementacja protokołu Gita w PHP mnie przerosły.
  • Był jeszcze mały projekt szkoleniowy, z którym chciałem się nauczyć Django i Reacta, ale spędziłem nad nim raptem 4 dni.

Wnioski

W eseju Katedra i bazar znajdujemy zdanie mówiące, że every good work of software starts by scratching a developer’s personal itch. Dobrze sprawdza się to w moim przypadku. Problem w tym, że wówczas, gdy personal itch przestaje mi dokuczać, także projekt przestaje być przeze mnie rozwijany. I tak stało się z kilkoma z nich. Dostrzegam tutaj wzorzec.

Wydaje mi się też, że nie idzie mi dobrze łączenie prób nauki technologii z próbami stworzenia wartości dla innych. Robiąc projekt, który ma mnie nauczyć technologii, z konieczności wybieram coś łatwiejszego w stosunku do tego, na co mógłbym się zdecydować, gdybym pracował ze znaną mi technologią. Tym samym, gdy zaznajomię się już z technologią, projekt przestaje być interesujący.

Kilka moich projektów wzięło się z problemów, które napotykałem w pracy. Przeglądarkę diffów plików PSD chciałem zrobić dlatego, że graficy, z którymi pracowałem, nie informowali o zmianach, które robili, ograniczając się do przesłania nowej wersji pliku. Reszty trzeba było się domyślać. qunit-desktop-notifications chciałem zrobić dlatego, że javascriptowe testy długo trwały i nie wiedziałem, kiedy się skończyły, więc chciałem mieć tę informację w notyfikacji dekstopowej. Implementację części Gita w PHP chciałem zrobić dlatego, że klienci, z którymi pracowałem, nie mieli Gita na swoich serwerach. Z czasem, gdy to przestawało być moim problemem, przestawałem się interesować projektami, które miały rozwiązać problem.

Wydaje mi się, że lepiej wychodzi mi robienie projektów, gdzie znajduję coś, czego jeszcze nie ma, co mnie fascynuje, i co jest w moim zasięgu. Wówczas jest szansa, że to skończę. A co do tych projektów, w których tylko uczę się technologii, być może po prostu trzeba zaakceptować, że będą one porzucone, gdy już z zaznajomię się z technologią, w której naukę celowałem.

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ń.