Czyli pułapki, przemyślenia podczas implementacji biblioteki dla pythona.
1. Brak dokumentacji
To chyba sprawia, że implementacja czegokolwiek staje się praktycznie niemożliwa bez grzebania w kodzie PHP (i debugowania go), kilku godzin klnięcia i zmieniania koncepcji całej biblioteki.
2. API nie jest REST
Aktualnie większość API tworzy się zgodnie z zasadami REST. Nie bez powodu. O zaletach REST napisano już wiele. Jest w zasadzie standardem, istnieje sporo bibliotek, które pomagają w implementacji klientów, wszystko jest intuicyjne. Poza tym bardzo łatwo cache'ować takie API. Sejmometr natomiast ma bardzo niewygodne RPC, dostosowane pod bibliotekę napisaną w PHP. Tak się robiło 10 lat temu. Jeżeli cały projekt ma coś osiągnąć, to API powinno być proste, intuicyjne, wygodne i łatwe do wykorzystania w każdym języku i środowisku.
3. Uwierzytelnianie
Powinno być proste. Najlepiej wykorzystać HTTP Auth. Pojedynczy token, bez sekretu i podpisywania zapytań. Zamiast tego wykorzystać HTTPS.
Aktualnie napisanie kodu do generowania sygnatur wymaga nawet kilku godzin oraz debugowania kodu biblioteki w PHP.
Aktualnie sposób podpisywania zapytań w połączeniu z formatem danych oczekiwanym przez PHP sprawia, że implementacja w każdym innym języku będzie upierdliwa. Musimy podpisać dane w stylu {"parametr": ["raz", "dwa", "trzy"], "inny_parametr": "1"}, natomiast wysłać dane w specyficzny dla PHP sposób: parametr[0]=raz¶metr[1]=dwa¶metr[2]=trzy&inny_parametr=1. PHP ma funkcję, która się tym zajmie. Żaden inny język nie. W pythonie - około 60-70 linii dziwnych przekształceń i kombinacji. Z dokumentacją wszystko było by prostsze! Warto też wspomnieć o użyciu JSON'a - otóż wygenerowany JSON nie w każdym języku wygląda tak samo. W Pythonie wygląda inaczej (dodatkowe spacje). Na szczęście Python sobie z tym radzi, ale nie wiem jak to wygląda z innymi językami.
Kolejna sprawa: parametry liczbowe czy boolean są przekazywane jako ciągi znaków, nawet do podpisu w sygnaturze!
Ja wiem - ktoś powie, że to nie są rzeczy, z którymi nie można sobie poradzić. Prawda. Max 100 linijek kodu i gotowe, ale to nie o to chodzi! To ma być nowoczesne, intuicyjne i łatwe. Uwierzytelnienie powinno zająć 1-2 linijki, nie 100.
4. To tylko RPC dla biblioteki w PHP
To widać na każdym kroku. Wysyłane dane - dokładnie tak jak je traktuje PHP. Parametry są liczbowe, ale po parse_str( http_build_query( $params ), $params ) wszystko zamienia się na ciągi znaków? No to po stronie serwera też traktujmy wszystko jako ciągi znaków! (to ma znaczenie tylko przy generowaniu sygnatury). Zwracane dane nazwijmy dokładnie tak jak klasy w bibliotece PHP. Nawet się z tym nie kryjmy - nazwijmy zmienną "item_class".
Popatrzmy na tego potworka:
{'gminy.adres': 'Baborów, Ul. Dąbrowszczaków 2a 48-120 Baborów',
'gminy.dochody_roczne': '16990949.47',
'gminy.fax': '0-77 403-69-28',
'gminy.liczba_ludnosci': '6315',
'gminy.nazwa': 'Baborów',
'gminy.nazwa_urzedu': 'Urząd Miejski w Baborowie',
'gminy.powiat_id': '62',
'gminy.powierzchnia': '117.00',
'gminy.telefon': '0-77 403-69-20',
'gminy.teryt': '160201',
'gminy.typ_id': '3',
'gminy.wojewodztwo_id': '8',
'gminy.wydatki_roczne': '16182887.10',
'gminy.zadluzenie_roczne': '-808062.37',
'powiaty.nazwa': 'głubczycki',
'powiaty.sejm_okreg_id': '21',
'wojewodztwa.nazwa': 'Opolskie'}
Tak wyglądają dane zwrócone dla gminy. W każdym normalnym API "powiaty" i "wojewodztwa" były by w osobnych obiektach. No i oczywiście niepoprawny Content-Type (text/html zamiast application/json) który może załatwić niektóre biblioteki do obsługi HTTP.
W zwróconych danych znajduje się też jakieś tajemnicze "mode" z możliwymi wartościami "DB" i "DBF". Z tego co zrozumiałem, to przy DBF każdy rekord przyleci do nas osobno zakodowany w JSONie. Super! JSON W JSONie. Kto wpadł na ten genialny pomysł?
5. Gdy klient robi coś nie tak
Kiedy klient zrobi coś nie tak, normalnym jest wysłanie kodu http innego niż 200 (najlepiej któryś z 5xx). Ewentualnie obiekt w jsonie z "status": "error". To API zwraca błąd w postaci tekstowej, z kodem 200 (OK). Nie ma nawet żadnej czytelnej wiadomości, która uświadomiła by mnie co robię źle. Po prostu "Error no. 356" czy "Error no. 425". Czasem nawet jest jeszcze ciekawiej:
Warning: mysqli::query(): Empty query in /home/www/s/_lib/mPortal/db.php on line 18
Myślicie, że to trudno wywołać? Skądże. Wystarczy się walnąć w nzwie datasetu. Oczywiście wysłane jako html. I szukaj wiatru w polu, bo stack trace pokazuje błąd gdzieś w bibliotece obsługującej API.
Podsumowanie
Podczas pisania tego skonsultowałem się z kilkoma osobami. Myślę, że aktualny stan najlepiej oddaje reakcja jednego z moich znajomych: "O matko Jap*cenzura*".
Prawdopodobnie o wielu rzeczach zapomniałem. Aktualne API to tylko RPC stworzone na potrzeby biblioteki w PHP. Z myślą głównie o wygodzie programisty, który to API tworzył, nie zaś ludzi, którzy będą z niego korzystać. Zostało stworzone według schematów, które aktualne były 10 lat temu, nie dzisiaj. Warningi z PHP pokazują tylko, że API nie jest w ogóle przetestowane. Jeżeli chcemy nowoczesnego e-państwa może powinniśmy zacząć od nowoczesnego API dla wszystkich ludzi, dla wszystkich platform, z którym poradzą sobie zarówno zaawansowani programiści, jak i Ci którzy na codzień są naukowcami czy kimkolwiek innym, kto w pracy programuje, ale programistą na codzień nie jest.
Nie zrozumcie mnie źle. Pomysł jest świetny. Zebraliście wszyskie dane i tu należą się same pochwały. Moje uwagi (domyślam się, że mogą zabrzmieć dosyć ostro) dotyczą tylko tej jednej warstwy - API. A projekt tej skali, który moim zdaniem jest ważny dla Polski musi mieć porządne, nowoczesne API. Także niech nikt się nie obraża, bo myślę, że zależy nam na tym samym.
Ten post powstał na prośbę Piotra Waglowskiego na Facebooku.