Komentarz dotyczący nowego API

265 views
Skip to first unread message

Rafał Stożek

unread,
Dec 5, 2012, 12:14:28 PM12/5/12
to sejmom...@googlegroups.com
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.

Proponuję obejrzeć tę prezentację: http://www.youtube.com/watch?v=NEWTPFzt2-E

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&parametr[1]=dwa&parametr[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.bip_www': 'www.bip.baborow.pl',
   'gminy.dochody_roczne': '16990949.47',
   'gminy.email': 'u...@baborow.pl',
   'gminy.fax': '0-77 403-69-28',
   'gminy.id': '20',
   'gminy.liczba_ludnosci': '6315',
   'gminy.nazwa': 'Baborów',
   'gminy.nazwa_urzedu': 'Urząd Miejski w Baborowie',
   'gminy.nts': '5163202013',
   '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.id': '62',
   'powiaty.nazwa': 'głubczycki',
   'powiaty.sejm_okreg_id': '21',
   'wojewodztwa.id': '8',
   '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
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head profile="http://www.w3.org/2005/10/profile"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"></head><body>Wystąpił nieoczekiwany błąd. Przepraszamy.<br/><a href="http://sejmometr.pl">Sejmometr</a></body></html>

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.
Message has been deleted

Piotr Waglowski

unread,
Dec 5, 2012, 1:31:17 PM12/5/12
to sejmom...@googlegroups.com
Dziękuję. Przeczytałem. Będziemy teraz się nad tym pochylać.

BTW - nikt u nas się nie obrazi. Krytyka jest dobra. Krytyka pozwala robić lepiej.
v.

Daniel Macyszyn

unread,
Dec 5, 2012, 4:51:57 PM12/5/12
to sejmom...@googlegroups.com

Witam

Dziękuje za wszystkie uwagi. Tak jak napisał Piotr Waglowski - krytyka pozwala robić lepiej.

Myślę, że większość problemów, których Pan doświadcza wynika z faktu, który trafnie opisał Pan w swoim mailu: to nie jest REST. To jest platforma, napisana specjalnie pod PHP, która do działania wykorzystuje pewne elementy koncepcji REST. Pan to formułuje jako zarzut, ale to po prostu jest taki produkt - przystosowany do środowiska PHP. Czemu tak jest?

Portal Sejmometr jest napisany w PHP. Projektując nową wersję portalu, robiliśmy to od podstaw z myślą o jakiejś formie publicznego API. Ostatecznie wpadliśmy na pomysł, że Sejmometr jako portal musi wykorzystywać do działania to same API, które później udostępnimy publicznie. To pozwoli utrzymać jego aktualność i sprawność. Tak też się stało. Portal Sejmometr używa do generowania stron z danymi dokładnie tego samego kodu, który udostępniliśmy jako ePF_API na GitHubie (jedyna różnica polega na tym, że w wersji serwerowej, metoda "call" klasy "ep_API", nie wykonuje żądań HTTP, tylko pobiera dane "wewnętrznie" w serwerze). W tym sensie portal Sejmometr jest pierwszą implementacją ePF_API, co pokazuje, że coś jednak z tą platformą da się zrobić :) Reasumując ePF_API to aplikacja PHP. Była pisana nie z myślą o wygodzie programisty, a z myślą o jak najprostszym działaniu w środowisku PHP. Według mnie ten cel udało się osiągnąć. W środowisku PHP ePF_API sprawuje się dobrze. Portal Sejmometr generalnie działa, a jego "front" działa głównie dzięki ePF_API.

To pozwala z innej perspektywy spojrzeć np na to, co Pan opisał jako "potworka" :) Ta tablica, to nie jest coś, co miałoby być wykorzystywane przez końcowego użytkownika API. To jest "surówka". Precyzyjnie rzecz ujmując: jest do jeden z rzędów odpowiedzi bazy SQL na zapytanie. Zapytanie jest robione na łączeniu tabel: gminy JOIN powiaty JOIN wojewodztwa. Stąd odpowiednie prefixy w nazwach pól. Zmierzam do tego, że te dane to tylko jeden, a co najważniejsze - niekońcowy łańcuch na drodze: bazy Sejmometru -> użytkownik ePF_API. Dane w tej surowej postaci są obrabiane w metodzie parse_data() klasy ep_Object i ona sprawia, że końcowy użytkownik może w środowisku PHP robić takie rzeczy jak:
$gmina->powiat() - zwraca obiekt klasy ep_Powiat, zawierający dane powiatu, do którego należy gmina.
Podobnie można wywoływać:
$gmina->wojewodztwo() i $powiat->wojewodztwo()
Na końcu dochodzimy do takiego stanu jak "w każdym normalnym API", tj. takiego w którym powiat i województwo są osobnymi obiektami.

Pozostałe uwagi:
Autoryzacja: oczywiściej lepiej byłoby, żeby autoryzacja opierała się o HTTP Auth. Obecne rozwiązanie z podpisywaniem żądań jest tymczasowe (choć niektóre duże portale, np Scribd.com, używają go jako docelowego). Zaimplementowanie HTTP Auth jest w naszych ToDos.

Bugi: wszystko co Pan napisał jest prawdą i nie ma co polemizować. Nie mamy w Fundacji zasobów, aby przeprowadzać testy. Te bugi, które Pan opisał, postaram się dziś / jutro wyeliminować.


Co z tym wszystkim zrobić?


Moim zdaniem powinniśmy zrobić tak:
To co teraz jest - generalnie, zostawiamy jako natywne API Sejmometru dla PHP i tylko dla PHP. Sejmometr jako portal działa i będzie działać w oparciu o to, więc wybór jest taki, że można to upublicznić lub nie. Wydaje mi się, że lepiej upubliczniać. Moim zdaniem w środowisku PHP jest to wygodne i pozwala szybko oraz intuicyjnie pobierać dane (patrz: http://sejmometr.pl/api/przyklady), a o to przecież chodzi (ale tylko w środowisku PHP).

Natomiast nad API PHPowym powinniśmy zbudować nowe API RESTowe z prawdziwego zdarzenia.

Wtedy infrastruktura "danowa" Sejmometru byłaby podzielona na 3 warstwy:
- warstwa #1
bazy SQL, pliki JSON, indeksy Lucene, itp - warstwa najniższego poziomu, na której bezpośrednio operują crawlery i analizatory Sejmometru. nie dostępna publicznie.

- warstwa #2
API PHPowe (to, co teraz funkcjonuje jako ePF_API) - warstwa średniego poziomu. natywne API Sejmometru działające tylko w środowisku PHP. Dostępne publicznie jako natywny klient PHP Sejmometru dla programistów PHP, którzy w poruszaniu się po danych chcą wykorzystywać obiektowość PHP.

- warstwa #3
REST API z prawdziwego zdarzenia. Technicznie - korzystałoby ona z warstwy #2, ale końcowy użytkownik nie odczuwałby tego, ponieważ byłaba prawdziwie niezależna od platformy i zgodna ze wszystkimi elementami koncepcji REST. Na jej podstawie, można by pisać klienty do innych języków programowania.


Co Wy na to?

Pozdrawiam i jeszcze raz dziękuje za uwagi. Wierzę, że wspólnie zrobimy z tego naprawdę fajną usługę :)

Daniel Macyszyn
Fundacja ePaństwo

Rafał Stożek

unread,
Dec 5, 2012, 6:40:51 PM12/5/12
to sejmom...@googlegroups.com
Czyli mode DBF nie muszę się w ogóle przejmować?

Całość może dawać radę jako API wewnętrzne, natomiast użycie go do zaimplementowania api rest może być miejscami problematyczne, np. skąd wiadomo, kiedy unieważnić cache API? Takie API musi się o wiele bardziej integrować z całą infrastrukturą. Myślę też, że nawet interfejs www skorzystałby na porządnym cacheowaniu danych. Poza tym dobrze zaprojektowane API REST po prostu musi być, bo bez niego ciężko wykorzystywać ponownie informacje.

Co do aktualnego API to przydało by się wprowadzić przynajmniej kilka zmian. Po pierwsze - porządne kody błędów, po drugie - cała komunikacja jsonem zamiast form-url-encode specyficznego dla PHP - pomoże to chociaż trochę osobm, które już teraz chcą zaimplementować to API. Po trzecie - nie zwracać błędów w HTMLu. Nawet przy poważnym błędzie. Można je zwracać w JSONie. Po czwarte - przy zwracanym jsonie nagłówek content-type: application/json. Po piąte - prosta dokumentacja - jakie są metody w RPC, jakie parametry, w jakim formacie zwraca.

Jeżeli chodzi o  podpisywanie - jasne, że serwisy typu scribd wykorzystują podpisy, ale nie robią tego na jsonie, tylko na połączonych ze sobą parametrach i nie przyjmują jednocześnie formatu danych specyficznego dla PHP. Bo to połączenie tych 3 czynników sprawia duży problem. No i nie zmienia to faktu, że jest to niewygodne.

Aktualny kod wymagany do wykonania zapytania do API można zobaczyć tutaj (base.py i utils.py, na razie tylko python 3): https://github.com/rafales/python-epanstwo-api - jest go zdecydowanie zbyt dużo jak na taką (wydawało by się) prostą czynność.

Pozdrawiam,

Rafał Stożek

2012/12/5 Daniel Macyszyn <dan...@epanstwo.org.pl>
Scribd.com

Daniel Macyszyn

unread,
Dec 6, 2012, 2:29:25 AM12/6/12
to sejmom...@googlegroups.com
Z DBF jest tak:

Wszystkie dane są zorganizowane w datasety. "mode" jest właściwością dataset'u. Niektóre dataset'y mają właściwość mode='DB', niektóre 'DBF'. Właściwość ta mówi serwerowi API skąd ma brać dane dla wywołań metody find_all() klasy ep_Dataset.

Jeśli mode='DB', to dane (wszystkie pola) są pobierane tylko i wyłącznie z bazy SQL. Serwer robi zapytanie SELECT (zwykle też są tam JOINy) i zwraca wyniki do klienta API. Następnie klient parsuje dane i dzieli je na obiekty (jeśli jest taka potrzeba).

Mode DBF to coś w rodzaju systemu cache'owania. Stosujemy ten tryb w przypadkach, gdy JOINy SELECTa były zbyt skomplikowane, miały zwracać dużą ilość pól, pochodzących z różnych tabel i zapytania działały nieakceptowalne wolno. Jeśli mode='DBF', to z bazy SQL pobierane są jedynie IDsy obiektów, które należy zwrócić. Następnie dla każdego ID, serwer otwiera i odczytuje plik JSON, który został wcześniej wygenerowany z bazy. Bezpośredni dostęp do tych plików miał być zablokowany, ale na potrzeby tej dyskusji - odblokowałem go. Proszę zerknąć:
http://sejmometr.pl/_api/data/prawo/1.json
Stąd też bierze się problem (choć moim zdaniem, to nie jest tak naprawdę problem :) JSONów wewnątrz JSONów. Serwer mógłby po swojej stronie dekodować pliki JSON i zwracać dane od razu w takiej formie jak przy trybie DB, ale postanowiliśmy, żeby dekodowanie JSONów było realizowane w kliencie API. Oczywiście można to zmienić w taki sposób, żeby serwer dekodował JSONy po swojej stronie i wtedy sposób pobierania danych, byłby przeźroczysty dla klienta API. Woleliśmy to jednak przerzucić na klienta (mniejsze obciążenia dla naszego serwera). Mode dla datasetów jest narzucony z góry po stronie serwera i nie można go sobie wybierać. Właściwość mode jest zwracana do klienta po to, żeby klient wiedział, czy ma dane parsować (DB), czy dostaje "gotowce" z plików (DBF).

To wszystko utwierdza mnie w przekonaniu, że obecna wersja PHP-owego klienta powinna pozostać wyjątkowym - natywnym klientem API i złym pomysłem jest przepisywanie jej z PHP do Pytona. Nie ma potrzeby, żeby Pyton bawił się parsowaniem danych w taki sposób jak robi to natywna biblioteka PHP. Trzeba napisać nową warstwę API - w pełni zgodną z REST. Postaramy się dzisiaj popracować nad problemami, które Pan zgłosił, a następnie zrobimy przymiarkę do tej nowej warstwy REST. Dam znać wieczorem.

Pozdrawiam
Daniel Macyszyn

Daniel Macyszyn

unread,
Dec 6, 2012, 4:46:13 AM12/6/12
to sejmom...@googlegroups.com
Dodałem na stronie http://sejmometr.pl/api/docs następujący opis:

"Platforma ePF_API w obecnej wersji nie jest platformą REST. Została napisana specjalnie pod środowisko PHP i jest to dokładnie ta sama biblioteka, której używamy do generowania stron portalu Sejmometr (jedyna różnica polega na tym, że wersja "serwerowa" nie używa protokołu HTTP do transportu danych). Obecnie pracujemy nad wdrożeniem drugiej platformy API, która będzie w pełni zgodna ze standardem REST i umożliwi stworzenie klientów dla środowisk innych niż PHP."

Mam nadzieję, że to pozwoli uniknąć nieporozumień.

Pozdrawiam
Daniel Macyszyn

Rafał Stożek

unread,
Dec 6, 2012, 3:03:44 PM12/6/12
to sejmom...@googlegroups.com
Proponuję, aby w trakcie fazy projektowej api restowego udostępnić jakąś wstępną dokumentację - aby inni mogli się wypowiedzieć i ew. zgłosić swoje pomysły czy uwagi - w końcu lepiej takie rzeczy załatwić zanim prace zostaną rozpoczęte.

Jutro prawdopodobnie usiądę i dokończę bibliotekę w Pythonie - co prawda jest to mordęga, ale w takim przypadku lepiej, aby przygotował to ktoś, kto sobie poradzi. No i chcę trochę poeksperymentować z tymi danymi.

Pozdrawiam.

Maciej Jakub Bańkowski

unread,
Dec 7, 2012, 11:23:52 PM12/7/12
to sejmom...@googlegroups.com
Popieram.

API powinno być RESTowe + wersja Alpha/Beta do wglądu dla użytkowników, to będzie mniej błędów i późniejszego poprawiania kodu produkcyjnego.

Hubert Mach

unread,
Dec 9, 2012, 8:28:32 AM12/9/12
to sejmom...@googlegroups.com
Witam.

Tak czytam i dochodzę do wniosku, że niepotrzebnie kruszymy kopie.
Czy nie prościej byłoby udostępnić crowlera, który te dane zbiera? A z poprzedniej wersji wiadomo, że nie robi tego w sposób doskonały - z tych czy innych przyczyn.
Co by się wtedy stało? Niby nic, ale jednak wiele. Więcej osób które by z niego korzystały mogłyby pomóc w jego udoskonalaniu.
Czy jest standard w jaki państwo publikuje dane? Jeśli nie, to więcej osób mogłoby wpływać na to, aby taki powstał.
Byłaby szansa na wypracowanie bardziej zaawansowanego API, niekoniecznie w strukturach epf.
Same informacje zostałyby bardziej rozproszone - wersja druga modułu dla drupala była przygotowywana tak, aby też mogła być źródłem danych - odciążyłoby to serwery fundacji.
Jestem przekonany, że byłoby to z pożytkiem dla wszystkich.

P.S.
"Weeks of programming can save you hours of planning." - ergo, lepiej zacząć od napisania dokumentacji (a takowa nie była od początku dostępna).
Na tym etapie wiele rzeczy można rozwiązać. Gdyby jeszcze była ona poddana do dyskusji, byłoby extra.

P.S. 2
Wizerunek.
Zostawiłem komentarz na stronie  http://lubimy.epanstwo.net/node/9 . Z ciekawości. Zastanawiałem się, co też fundacja robi z komentarzami, kiedy nie daje sobie nawet szansy na kontakt z autorem wpisu?
Komentarz się nie ukazał.

Pozdrawiam.

--
H.

Piotr Waglowski

unread,
Dec 10, 2012, 10:25:07 AM12/10/12
to sejmom...@googlegroups.com

P.S. 2
Wizerunek.
Zostawiłem komentarz na stronie  http://lubimy.epanstwo.net/node/9 . Z ciekawości. Zastanawiałem się, co też fundacja robi z komentarzami, kiedy nie daje sobie nawet szansy na kontakt z autorem wpisu?
Komentarz się nie ukazał.


Tak. Już się ukazał, ale istotnie mam tu problem. Odpaliliśmy "lubimy", ale doszliśmy do wniosku, że znacznie lepiej byłoby jednak te wszystkie materiały trzymać na Fundacyjnej stronie. Będziemy musieli podjąć (spójną) decyzję, jak się komunikować.
v.
Reply all
Reply to author
Forward
0 new messages