Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> [LR] Budowanie API
markonix
post 26.11.2017, 02:19:47
Post #1





Grupa: Zarejestrowani
Postów: 2 707
Pomógł: 290
Dołączył: 16.12.2008
Skąd: Śląsk

Ostrzeżenie: (0%)
-----


Zabieram się do wystawienia API do istniejącej już aplikacji, API będzie zarówno pod appkę jak i strony www.
Chciałbym aby to było dobre, RESTowe, użyteczne i elastyczne API, a nie kilka sztywnych metod.
Jakieś doświadczenia jak zbudować dobre API, z którego byście sami z przyjemnością korzystali?

Kilka ważnych aspektów:
- autoryzacja
- wersjonowanie
- spójny format błędów
- kontrola nad zwracanymi danymi (zarówno wybór pól jak i relacji)
- limitowanie i paginacja
- możliwość generowanie zaawansowanych metod typu find, search (szukanie po polach za pomocą różnych warunków, które można użyć w where())
- dokumentacja (generator?)

Pod większością względów podoba mi się API wFirmy (system do faktur):
https://doc.wfirma.pl/#h2-Komunikacja-h3-Ko...nie-zapyta-find
Poza troszkę zagmatwanym formatem danych, zwracanych przez te API i brakiem wersjonowania to jest to dla mnie wzór, który chciałbym osiągnąć.

Zastanawiam się czy znajdę gotowy szkielet takiego API czy muszę to implementować wszystko samemu, na poziomie kontrolerów i repozytoriów?
W L5.5 jest kilka dodatków typowo pod API np. Responses ale i tak wciąż jest sporo pracy.

Ten post edytował markonix 26.11.2017, 02:21:48


--------------------
Go to the top of the page
+Quote Post
r4xz
post 26.11.2017, 09:16:15
Post #2





Grupa: Zarejestrowani
Postów: 673
Pomógł: 106
Dołączył: 31.12.2008

Ostrzeżenie: (0%)
-----


Polecam po prostu zapoznać się z panującymi standardami. Jeśli o mnie chodzi to bardzo podobała mi się ta infografika.

Dokumentację polecam robić w swaggerze, po wygenerowaniu wygląda to bardzo dobrze i jest funkcjonalne (do tego jest to już sprawdzone i dosyć dojrzałe rozwiązanie). Sam przykład wykorzystania swaggera w projekcie:
  1. /**
  2.   * @SWG\Post(
  3.   * path="/schedules/{schedule_id}/close",
  4.   * consumes={"multipart/form-data"},
  5.   * tags={"Schedules"},
  6.   * security={{"api_key": {}}},
  7.   * @SWG\Parameter(in="path", name="schedule_id", required=true, type="integer"),
  8.   * @SWG\Response(response="200", description="Schedule successfully opened."),
  9.   * @SWG\Response(response="401", description="Unauthenticated."),
  10.   * @SWG\Response(response="403", description="Invalid permissions."),
  11.   * @SWG\Response(response="404", description="Schedule with given id not found."),
  12.   * @SWG\Response(response="409", description="Schedule has been already opened."),
  13.   * )
  14.   */
  15. public function close(Schedule $schedule)
  16. {
  17. // ...
  18. }

Oczywiście z tego kodu generuje się ładna dokumentacja, zobacz np. ten przykład. Jest też możliwość generowania klientów dla wielu języków programowania, dzięki temu nikt nie musi się zastanawiać co robi Twoje API, analizować dokumentacji itp. ponieważ ma to w postaci ładnych metod gotowych do użycia (choć wtedy trzeba to trochę łądniej napisać niż w moim przykłądzie).

Co do samej obsługi parametrów field, sort, filter itp. to faktycznie jest to problem, istnieją jakieś rozwiązania dla Laravel jednak nie wszystkie mnie satysfakcjonowały, właściwie to z każdego wydobyłbym coś innego. Swego czasu napisałem nawet bibliotekę która pomaga mi w pisaniu tego typu api (link do githuba). Oczywiście przez to że z niej korzystam to znam kilka słabych stron tej biblioteki, ale niestety ze względu na natłok obowiązków nie doczeka się ona drugiej wersji wcześniej jak w lipcu/sierpniu.

I na koniec, bo nie wiem czy słyszałeś, a tym bardziej myślałeś o GraphQL? Jeśli nie słyszałeś to też zobacz jakie to ma możliwości i podejmij decyzję REST vs GraphQL


--------------------
Go to the top of the page
+Quote Post
markonix
post 26.11.2017, 16:50:10
Post #3





Grupa: Zarejestrowani
Postów: 2 707
Pomógł: 290
Dołączył: 16.12.2008
Skąd: Śląsk

Ostrzeżenie: (0%)
-----


Twoja biblioteka właśnie jest czymś czego szukam, fajnie by było znaleźć coś podobnego tylko bardziej popularnego/rozwijanego z community.
Wzorowałeś się może jakiejś innej bibliotece? Najczęściej trafiam na "Dingo" ale ma to dla mnie mało czytelną dokumentacje i nie jestem pewien czy to jest to czego szukam.

GraphQL wygląda fajnie, generalnie czytałem parę artykułów o innych podejściach w API czy przy budowaniu repozytoriów niż takiego typowo RESTowe, ale na razie bym został na REST ze względu na ograniczony czas na zbudowanie API i przyzwyczajenie osób, które będą z tego API korzystały.


--------------------
Go to the top of the page
+Quote Post
r4xz
post 26.11.2017, 17:12:53
Post #4





Grupa: Zarejestrowani
Postów: 673
Pomógł: 106
Dołączył: 31.12.2008

Ostrzeżenie: (0%)
-----


Nie, niestety nie widziałem podobnej biblioteki, raczej patrzyłem na sposób definiowania GraphQL i zastanawiałem się czemu czegoś podobnego nie można znaleźć dla RESTa, aby tak z automatu mieć obsługę chociaż tych podstawowych rzeczy jak np. parametru fields/only, które mogłoby nawet naiwnie działać w post-processingu i wycinać przed wysłaniem do użytkownika okrojone dane.

Sprawdzam właśnie jak to robią w innych projektach:
Cachet - ręczna zabawa,
Laravel Horizon - może nie jest to fragment REST API, ale też idzie ręcznie
Flarum - https://github.com/tobscure/json-api


--------------------
Go to the top of the page
+Quote Post
markonix
post 26.11.2017, 17:28:08
Post #5





Grupa: Zarejestrowani
Postów: 2 707
Pomógł: 290
Dołączył: 16.12.2008
Skąd: Śląsk

Ostrzeżenie: (0%)
-----


A jak Twoja biblioteka sobie radzi z L5.5?


--------------------
Go to the top of the page
+Quote Post
r4xz
post 26.11.2017, 17:45:52
Post #6





Grupa: Zarejestrowani
Postów: 673
Pomógł: 106
Dołączył: 31.12.2008

Ostrzeżenie: (0%)
-----


Raczej nie powinno być problemów, prawdę mówiąc pamiętam tylko o wersji Laravela w aktualnie rozwijanym projekcie i jest to wersja 5.4, a projekt do najmłodszych nie należy ponieważ ma już za sobą migrację z 5.3 smile.gif

Uprzedzam tylko, że są problemy z relacjami przechodnimi jak np. "user.messages", ale dla zwykłych relacji typu "user" lub "messages" działa poprawnie i nie napotkałem problemów. Np. ?limit=messages.5 zadziała, ale ?limit=messages.files.5 już nie jestem pewień, obsługa niektórych parametrów dla tego typu przypadków zawodzi.

PS Po magisterce musiałbym przysiąść trochę do tej biblioteki i faktycznie mogłoby powstać z tego coś wartościowego. Tzn. na moje potrzeby już jest i wykorzystuję ją zarówno na uczelni jak i w pracy (choć niestety tylko jeden z tych projektów generuje naprawdę ciekawe przypadki użycia).

Ten post edytował r4xz 26.11.2017, 17:48:11


--------------------
Go to the top of the page
+Quote Post
markonix
post 16.12.2017, 00:45:00
Post #7





Grupa: Zarejestrowani
Postów: 2 707
Pomógł: 290
Dołączył: 16.12.2008
Skąd: Śląsk

Ostrzeżenie: (0%)
-----


Tak więc ostatecznie skorzystałem z Dingo, ma on tam dokumentowanie ale zgodnie z sugestią poszedłem w Swaggera, szkoda tylko, że trzyma się go na gałęzi 2.0.
Jeżeli chodzi o queries API to ciągle stoję w miejscu.

Nie potrafię znaleźć odpowiedniego narzędzia. W pewnym momencie myślałem, że nie będę musiał nawet go szukać bo mam go pod nosem bo używam Repository Pattern i biblioteki:
https://github.com/andersao/l5-repository#u...requestcriteria
No i mówię świetnie, wystarczy takie coś:
  1. public function index()
  2. {
  3. $this->playerRepository->pushCriteria(app('Prettus\Repository\Criteria\RequestCriteria'));
  4. return $this->playerRepository
  5. ->all();
  6. }

No i początkowo pełny entuzjazmu patrzę i faktycznie działa.. with fajnie, filter w porządku, search już się zaczynają schody, jakieś dziwne podejście do wyszukiwania po wielu polach, które musisz jeszcze zadeklarować tak jakby nie można było szukać po dowolnym atrybucie i w jakikolwiek sposób (nie tylko = ale też < > != itd.) Z sortowaniem też słabo bo nie da się posortować wg dwóch pól, a semantyka średnia. Brak limit(). Żeby limitować muszę już zamiast all() użyć paginate(), który jest nawet spoko bo dodaje linki prev/next, sumę itp. ale to już zmienia mocno format danych, co jest przesadą jakbym chciał np. ostatnie 5 rekordów posortowanych wg daty.

Próbowałem też inne ale większość wymaga aby przekazywać jako argumenty QueryBuilder'a. Częściowo dla mnie zrozumiałe ale nie bardzo bym chciał pomijać repozytoria. Czy u Ciebie da się używać repozytory pattern i Twojej biblioteki? Jakoś w przykładach u Ciebie nie do końca widzę jaką metodę wstrzykiwania przyjąłeś.

Chyba się udało z biblioteką:
https://github.com/marcelgwerder/laravel-api-handler

  1. return $this->myRepository->scopeQuery(function($query) {
  2. return ApiHandler::parseMultiple($query)->getBuilder();
  3. })->getBySomethingAndOtherLogic($someValue);


Można używać repo jednocześnie, i kontrolery względnie zostają czyste. Biblioteczka też widzę że jest od czasu do czasu aktualizowana.

Nawet jeszcze ładniej się da jak się utworzy własną metodę:
  1. return $this->myRepository->scopeQueryParametersMultiple()->getBySomethingAndOtherLogic($someValue);


Ten post edytował markonix 16.12.2017, 12:11:05


--------------------
Go to the top of the page
+Quote Post
r4xz
post 16.12.2017, 12:36:08
Post #8





Grupa: Zarejestrowani
Postów: 673
Pomógł: 106
Dołączył: 31.12.2008

Ostrzeżenie: (0%)
-----


W mojej bibliotece działasz na QueryBuilder, zapytanie tworzysz normalnie tylko pomijasz etap pobrania wyników, biblioteka sama dokleja potrzebne warunki i pobiera wyniki:

  1. rest()->collection(User::where('kolumna', 'wartosc')->otherScopes());


W Twoim przykładzie trochę średnio zrozumiałem problem z limit, jeśli wpierw pobierasz wszystkie rekordy, a dopiero potem chcesz przycinać wyniki to jest to strasznie niewydajne, dlatego konieczne jest operowanie w przypadku parametru limit na QueryBuilder. Ale bardzo ciekawe jest to co tu piszesz, może zainpiruję się Twoimi przemyśleniami i w przyszłości zrobię wersję która pokrywa więcej przypadków użycia, póki co jest ściśle dopasowana pod moje wymagania smile.gif


--------------------
Go to the top of the page
+Quote Post
markonix
post 16.12.2017, 17:09:08
Post #9





Grupa: Zarejestrowani
Postów: 2 707
Pomógł: 290
Dołączył: 16.12.2008
Skąd: Śląsk

Ostrzeżenie: (0%)
-----


O limitowanie mi chodzi o to, że w tym moim repozytorium nie ma w ogóle opcji limit jako tako czyli nie ograniczysz w prosty sposób listy zwróconych obiektów. Oczywiście wszystko na poziomie bazy, na razie nie myślałem o operowaniu na pobranej kolekcji ale już załamany powoli o tym myślałem (bo generalnie wszystkie where, limit, load - można zrobić na kolekcji, a na pewno byłoby to prostsze w osiągnięciu), na szczęście znalazłem ludzkie rozwiązanie.
Jeżeli chciałbym limitować kolekcję to muszę użyć paginacji czyli zupełnie innej metody niż zwykłe all() czy get(). Ta metoda ma już limit i offset ale zamiast kolekcji zwraca obiekt gdzie sama kolekcja ląduje w atrybucie data, a oprócz tego masz jeszcze atrybuty dotyczące paginacji czyli sumaryczna liczba rekordów, link do poprzedniej i następnej "strony" wyników co nawet i w API nawet może się przydać ale nie podoba mi się, że użycie limit powoduje takie duże zmiany w zwracanych danych. O wiele lepsze by było aby limit zostawić w spokoju, a dodać atrybut paginate=10,5, który już użytkownik API świadomie przesyłał i wiedział, że otrzyma zupełnie inny zestaw danych.

Jeżeli Twoja biblioteka ma metodę do zwrócenia jeszcze instancji QueryBuilder to prawdopodobnie też bym mógł jej użyć razem z repozytorium tak jak na przykładzie. Na modelach nie operuje zupełnie w kontrolerach.
Repozytoria u mnie już zwracają kolekcje, nie używam raczej get() czy first() w kontrolerach co też jeszcze lepiej je robi klarownymi.

Ten post edytował markonix 16.12.2017, 17:11:03


--------------------
Go to the top of the page
+Quote Post
r4xz
post 17.12.2017, 10:49:56
Post #10





Grupa: Zarejestrowani
Postów: 673
Pomógł: 106
Dołączył: 31.12.2008

Ostrzeżenie: (0%)
-----


Cytat(markonix @ 16.12.2017, 17:09:08 ) *
O limitowanie mi chodzi o to, że w tym moim repozytorium nie ma w ogóle opcji limit jako tako czyli nie ograniczysz w prosty sposób listy zwróconych obiektów.

To jak to do tej pory realizowałeś dla zapytań które jednak musiały jakoś ograniczać liczbę wyników?

Cytat(markonix @ 16.12.2017, 17:09:08 ) *
Jeżeli Twoja biblioteka ma metodę do zwrócenia jeszcze instancji QueryBuilder to prawdopodobnie też bym mógł jej użyć razem z repozytorium tak jak na przykładzie. Na modelach nie operuje zupełnie w kontrolerach.

Tak jak napisałem u mnie to działa trochę inaczej, wpierw trzeba przygotować odpowiedni QueryBuilder który przekazuje się w wywołaniu metody, doklejane są do niego odpowiednie warunki i zwracany wynik. Ty widzę szukasz bardziej czegoś co wpierw doklei Ci wszystkie warunki, zwróci QueryBuilder, a dopiero potem będziesz na nim operował. Takie rozwiązanie ma pewne wady jeśli chodzi o implementację tego jako bibliotekę, ponieważ łatwo o sytuację w której programista wykluczy pola które są potrzebne do złączenia relacji. W swojej implementacji tyle pól ile tylko możliwe staram się już wycinać na poziomie zapytania, jednak jest też pewien mały postprocessing który usuwa np. id (a którego nie mogłem wcześnieje usunąć ponieważ Eloquent nie umiałby powiązać relacji).


--------------------
Go to the top of the page
+Quote Post
Pyton_000
post 17.12.2017, 11:07:04
Post #11





Grupa: Zarejestrowani
Postów: 8 068
Pomógł: 1414
Dołączył: 26.10.2005

Ostrzeżenie: (0%)
-----


A ja tak z innej beczki trochę skoro już o api mowa.
Stosujecie Transformatory? Chodzi mi o transformacę danych z BD na dane do API włącznie z przemianowaniem pól. (tak to się bodaj nazywało)
Go to the top of the page
+Quote Post
markonix
post 17.12.2017, 18:50:36
Post #12





Grupa: Zarejestrowani
Postów: 2 707
Pomógł: 290
Dołączył: 16.12.2008
Skąd: Śląsk

Ostrzeżenie: (0%)
-----


Cytat(r4xz @ 17.12.2017, 10:49:56 ) *
To jak to do tej pory realizowałeś dla zapytań które jednak musiały jakoś ograniczać liczbę wyników?

  1. $repository->getLastPosts($limit = 10)

A potem w kodzie repozytorium możesz operować na Eloquent także więc nie ma aż takiego dużego problemu.


Cytat(r4xz @ 17.12.2017, 10:49:56 ) *
Tak jak napisałem u mnie to działa trochę inaczej, wpierw trzeba przygotować odpowiedni QueryBuilder który przekazuje się w wywołaniu metody, doklejane są do niego odpowiednie warunki i zwracany wynik. Ty widzę szukasz bardziej czegoś co wpierw doklei Ci wszystkie warunki, zwróci QueryBuilder, a dopiero potem będziesz na nim operował. Takie rozwiązanie ma pewne wady jeśli chodzi o implementację tego jako bibliotekę, ponieważ łatwo o sytuację w której programista wykluczy pola które są potrzebne do złączenia relacji. W swojej implementacji tyle pól ile tylko możliwe staram się już wycinać na poziomie zapytania, jednak jest też pewien mały postprocessing który usuwa np. id (a którego nie mogłem wcześnieje usunąć ponieważ Eloquent nie umiałby powiązać relacji).

Tzn. przez programistę rozumiesz użytkownika API, który selectem/filterem wybierze zbyt mało pól. Masz racje - przetestowałem i faktycznie filtr powoduje, że zamiast relacji mamy null gdy wytniemy klucz.
Jednak co dziwne autor tej biblioteki sam napisał:
Cytat
Note: Whenever you limit the fields with _fields in combination with _with. Under the hood the fields are extended with the primary/foreign keys of the relation. Eloquent needs the linking keys to get related models.

Choć to nie działa albo chodziło mu tylko o to, że _fields nie wytnie atrybutów, które są relacjami, ale co mi z nich gdy zawierają null..

Co do transformatorów, które są dostępne od ręki w L5.5 jako Responses nie korzystałem bo użytkownicy API póki co nie narzekali na format zwracanych danych. Myślę, że to jednak od nich powinna wyjść ta sugestia np. gdyby mój format nie był zgodny z jakimś standardem (mówię np. o datach czy liczbach). Jak zwracam im za dużo to zawsze mają możliwość użycia wspomnianego limitowania. Nie wiem czy pod transformatory podchodzą dekoratory, które doklejają dane do odpowiedzi i to pewnie użyję właśnie w paginacji gdzie będą dołączone meta dane.

Ten post edytował markonix 17.12.2017, 18:51:17


--------------------
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Wersja Lo-Fi Aktualny czas: 23.04.2024 - 16:00