Používáte Nette pro RESTful API?

Filip Klimeš
Nette Blogger | 156
+
+3
-

Ahoj,

úvodem bych se chtěl omluvit za mojí nízkou/žádnou aktivitu v posledních měsících. Makal jsem na bakalářce a v práci toho taky nebylo málo. Teď, když jsem dodělal bakalářku a učím se na státnice, přemýšlím, co dělat v létě. Opensource mě baví a Nette je takové vděčné, tak bych rád dál přispíval. Protože jsem z psaní textu docela unavený, chtěl bych kromě psaní dokumentace zkusit naprogramovat nějaký kus hezkého kódu, který by mohl být někomu třeba i prospěšný.

Přemýšlel jsem co Nette chybí. Za mě je to momentálně hezká a ucelená podpora RESTful API. Existují sice balíčky drahak/Restful a newPOPE/Nette-RestRoute, ale nevím, jak moc jsou používané na reálných produktech. Tuším, že v rámci Nette PRO se o REST podpoře psalo, pokud si správně pamatuju, ale nevím, jestli to byl jen nápad, nebo existuje reálná poptávka.

Mám v hlavě pár nápadů, které bych chtěl realizovat:

  • Automatické generování resources/endpointů z entit modelu (Doctrine)
  • Automatické mapování dat z requestu na entity
  • Automatická serializace entit/DTO do odpovědi
  • Adaptivita API pomocí filtrů (Chain of Responsibility), kterými by se předával request. Např. filtry pro caching, security (ACL), serialization. Knihovna by mohla poskytovat základní implementaci filtrů, kterou by si uživatel mohl vyměnit, rozšířit nebo přeskládat.
  • Nějak hezky vyřešit verzování API (tohle ještě nemám moc promyšlené)
  • To všechno komfortně za využití přístupů, které znáte z Nette.

Než sepíšu detailnější RFC, chtěl jsem se zeptat: Jak řešíte REST API Vy?. Co třeba větší společnosti jako Rohlík, DámeJídlo, Slevomat? A co ty menší? Používáte vůbec pro tento účel Nette, nebo radši sáhnete po nějakém jiném PHP frameworku, popř. po jiném jazyce? Máte nějaké specifické požadavky nebo problémy, které vás brzdí, ale nemáte čas je řešit?

Předem díky za konstruktivní odpovědi :)

Editoval Filip Klimeš (27. 5. 2016 18:06)

Pavel Janda
Člen | 977
+
+2
-

Tak určitě je toho víc, co stojí za zmínku. :P Například:

– ublaboo/api-router (http://ublaboo.org/api-router/)
 – ublaboo/api-docu (http://ublaboo.org/api-docu/)

Jsem fakt smutnej, že se to v tvém příspěvku neobjevilo, jakto? Kde o tom chybí zmínka?

Jedou mi tyhle knihovny na pár menších a pár velkých projektech. A jedou.

Co se týče autorizace, nerad cpu věci s tím související do takových knihoven. A navíc je to na pár řádek kódu. Autorizace přes token se prdne do BasePresenteru a basta. S routováním a pod to nemá nic společného.

V čem tyto apíčkové ublaboo knihovny excelují je jedno – dokumentace (jedna routa, celé api). Doporučuju prostudovat web – je taky jenom pár řádek textu.

Taky by mě docela zajímalo, jestli někdo automaticky mapuje rest api na entity db. Určitě ne POST, ale GET by mohl být zajímavý a přinést užitek spolu s úsporou času!

Verzování api snad není moc „řešitelné“… – To by mě zajímalo, co tím autor myslel?

Editoval Pavel Janda (27. 5. 2016 17:10)

Filip Klimeš
Nette Blogger | 156
+
0
-

Pavel Janda napsal(a):

Jsem fakt smutnej, že se to v tvém příspěvku neobjevilo, jakto? Kde o tom chybí zmínka?

Omlouvám se, nevěděl jsem o nich. Koukám, že jsou ale poměrně nové. Jak jsem psal, posledních několik měsíců jsem neměl čas sledovat dění kolem Nette, takže jsem nepostřehl ani tyto frameworky.

Co se týče autorizace, nerad cpu věci s tím související do takových knihoven. A navíc je to na pár řádek kódu. Autorizace přes token se prdne do BasePresenteru a basta. S routováním a pod to nemá nic společného.

To je autentifikace. Autorizací jsem myslel např. přístup k různým resources podle uživatelské role.

Taky by mě docela zajímalo, jestli někdo automaticky mapuje rest api na entity db. Určitě ne POST, ale GET by mohl být zajímavý a přinést užitek spolu s úsporou času!

Proč myslíš, že POST ne? GET se dá řešit velmi snadno nějakou serializací, pro POST mapování jsem toho zatím moc nenašel, ale určitě to půjde řešit.

Filip Klimeš
Nette Blogger | 156
+
0
-

Pavel Janda napsal(a):

– ublaboo/api-docu (http://ublaboo.org/api-docu/)

Jak tak koukám na API Docu: Je to hezký nápad, jen by to ještě šlo vylepšit o automatické generování té dokumentace z Doctrine entit například. Takhle musí vývojář při změně upravit entitu a zároveň endpoint + jeho dokumentaci. Nebylo by fajn tohle moct upravovat jen na jednom místě? :)

Editoval Filip Klimeš (27. 5. 2016 17:19)

Pavel Janda
Člen | 977
+
0
-

@FilipKlimeš To se děje na jednom místě. Vše máš v anotaci presenteru/akce. Jak definici routy, tak description.

Dokumentace je generována právě z tohoto. Z jednoho místa. A myslím, že není dobrý nápad mít dokumentaci API generovanou od entit.. Moment – co máš na mysli entitami? Endpointy api, nebo orm?

A myslím, že POST ne, protože skoro pořád potřebuješ dělat víc věcí najednou. Jednou poslat obrázek na AWS, jednou pushnout notifikaci, jednou poslat sms a jindy pustit vodotrysk. A tohle všechno hnát přes entitu .. to by se mi nelíbilo.

Na druhou stranu – pokud bys dokázal nějak pěkně implementovat ty filtry, mohla by tomu pomoci právě kombinace filtrů, eventů a pod.


Edit: ORM, už to vidím.

Editoval Pavel Janda (27. 5. 2016 17:23)

Filip Klimeš
Nette Blogger | 156
+
0
-

Pavel Janda napsal(a):

@FilipKlimeš To se děje na jednom místě. Vše máš v anotaci presenteru/akce. Jak definici routy, tak description.

Rozumím, spíš jsem tím myslel tu integraci s ORM. Z mé zkušenosti podoba endpointů často kopíruje právě podobu ORM entit. Navíc jeden endpoint často odpovídá jedné entitě. (Možná se pletu, tak mě kdyžtak vyveďte z omylu)

A myslím, že POST ne, protože skoro pořád potřebuješ dělat víc věcí najednou. Jednou poslat obrázek na AWS, jednou pushnout notifikaci, jednou poslat sms a jindy pustit vodotrysk. A tohle všechno hnát přes entitu .. to by se mi nelíbilo.

Na druhou stranu – pokud bys dokázal nějak pěkně implementovat ty filtry, mohla by tomu pomoci právě kombinace filtrů, eventů a pod.

Přesně tak, tyhle věci by šlo podle mě hezky řešit filtry :). Popřípadě pouštět eventy, např. při vytvoření nové entity, updatu atp.

Editoval Filip Klimeš (27. 5. 2016 17:31)

kleinpetr
Člen | 480
+
0
-

@PavelJanda Ahoj, prosimtě můžu se zeptat jak tedy řešíš tu autentifikaci popřípadě autorizaci ? Pokud jsem to pochopil, tak to řešení nemá co dělat s tím addonem od ublaboo, takže si nějak posíláš v hlavičce token a v BasePresenteru si ho vytáhneš a provedeš kontrolu, nebo ?

Díky za info.

Petr

Pavel Janda
Člen | 977
+
+1
-

@kleinpetr
1, Přesně tak, api-router (doporučuji začít stavět na ořezaném web-projectu: composer create-project ublaboo/api-router-project) je pouze router. Mně se nelíbí postup, kdy routovací vrstva řeší přihlášení a podobné věci.

2, Autorizace se dá řešit na spoustě míst a různým způsobem (v závislosti na typu aplikace). Nejčastěji bych asi vytvořil AbstractController imlpementující IPresenter (to je snad jen metoda ::run(Application\Request $request): Application\IResponse) a potom kontroloval hlavičky hned za začátku této metody (případně v nějakém middlewaru – odbočka k zajímavé knihovně z balíčku namespacu contrubutte). Například aplikací definované tokeny.
Nebo může jít o SPA/PWA/blabla prostě aplikaci v JS, která se jednou přihlásí k backendu a už jede přihlášená. Ta by poslala poprvé třeba username/password a o víc by se nestarala, jelikož moje aplikace by uživatele normálně přihlásila pomocí Nette\Security\User a v dalších requestech by byl uživatel automaticky přihlášen (bacha, httpOnly cookiny).

Tohle téma by vyneslo na samotnou přednášku. Kdybys měl konkrétnější dotaz, tak se ptej. :)

kleinpetr
Člen | 480
+
0
-

Díky za info, vyzkouším ten osekaný projekt, chtěl jsem použít i api-docu, ale zjistil jsem, že je required php7, a tuším, že i pro api-route 2.0 je required také :/

Nicméně mohl bys mi dát nějaký nástřel tý autorizace ? Zatím jsem restko neřešil, ale teď ho potřebuju k jednoduchý aplikaci, kde bude 1 presenter a max 5 akcí. Takže mi stačí nějaký standartní typ autorizace, ale jak říkám, nikdy jsem se tím nezabýval dopodrobna, takže by mi píchla každá rada z tvých zkušeností :)

Pavel Janda
Člen | 977
+
0
-

@kleinpetr Zeleží na tom – kdo bude k tvému api přistupovat. JavaScript? Jiný backend? Mobilní aplikace?

kleinpetr
Člen | 480
+
0
-

klasické web aplikace opět v nette :)

Pavel Janda
Člen | 977
+
0
-

@kleinpetr Pak bych klidně využil nativní http autorizaci, případně vlastní http hlavičku a autorizační token, který budou obě aplikace sdílet (např v proměnné prostředí nebo ve vendoru [radši tu proměnnou prostředí]).

Client appka vytvoří request s http auth, tvoje appka se v každém requestu od clienta koukne na http auth hlavičku, ověří a jede dál (případně request zamítne při nevalidní autorizaci).

kleinpetr
Člen | 480
+
0
-

Jojo, něco takového jsem měl na mysli. Viděl bych to asi tak, že budu mít nějaký api_key, který bude znát client appka, a s ním se mě dotáže na nějaký token, který bude mít nějakou platnost a další requesty budou mít v hlavičce, jak api_key, tak token. Po vypršení tokenu vrátím nějaké info.

Takže si myslím, že když půjdu cestou, že v každé action metodě si zavolám nějakou kontrolu hlaviček a tokenů, tak by to mělo být dostačující, je to tak ?

Co jsi přesně myslel tím, v proměnné prostředí ?

Editoval kleinpetr (31. 8. 2017 14:26)

Pavel Janda
Člen | 977
+
0
-

@kleinpetr No, v action metodě by ses zbytečně opakoval.. Spíš si to prdni někam do předka, který to bude kontrolovat pro všechny akce společně. A teprv po kontrole autorizace se to rozstřelí do actionMetod.

Proměnnou prostředí jsem myslel toto: Já nerad dávat různé konfigurační parametry přímo do gitu projektu. Je to tam zbytečně na očích. Jsou to parametry typu: přístupy do databáze, různé tokeny apíček, přístupy do redisu, hesla k smtp schránkám a podobně. Dávám je pak do ENV vars, kam může šahat vždy jen master projektu. Ale je to samozřejme zbytečné, pokud děláš na appce sám. To je vhodné řešení například pro tuny aplikací startovaných z docker-compose.

kleinpetr
Člen | 480
+
0
-

Díky :)

Tak jsem si načetl projekt, nastavil konečně php7.1, nastavil vhosta a teď zkouším ten loginPresenter, přepsal jsem v anotaci POST na GET, ale pořád mi hází NO ROUTE…

Ještě jsem se chtěl zeptat jak přesně tam funguje ten ErrorController a jak ideálně testovat ostatní metody kromě GET na localu.

Všiml jsem si, že v configu přidáváš controllery do services, tudíž je asi nenačítá loader automaticky.?
Ještě mě tak napadá, jak tedy řešíš login a autorizaci ty ? Je jedna z možností poslat POSTEM na login credentials, zkontrolovat v db a poté vytvořit nějaký token, ten vrátit a s tímto tokenem dělat další requesty.
Nebo jak by to fungovalo se session kdybych k tomu přistupoval jako standartní login, mám tušení, že to úplně nebude stejný princip.

Díky za rady.

Editoval kleinpetr (31. 8. 2017 21:09)

Pavel Janda
Člen | 977
+
0
-

@kleinpetr
1, Smazal jsi cache?
2, Mrkni na Postmana (https://www.getpostman.com/) – žůžo klient pro testování apíček
3, ErrorController klidně vynech. Nechá se na něj třeba přesměrovat, ale být tam asi nemusí (asi ?)
4, Nenačítá
5, Jestli budeš přihlašovat pokaždé jiného uživatele, tak normálně volej login a ano, udělej si nějaký authToken, který vrátíš client apíčku v odpovědi – tento token bude muset client apíčko při každém dalším requestu vracet.
Pokud se nebudou přihlašovat uživatelé, ale prostě jedno apíčko, stačí kontrola token == token..
Session bych do toho asi netahal.

Editoval Pavel Janda (31. 8. 2017 22:04)

kleinpetr
Člen | 480
+
0
-

@PavelJanda Díky moc, smazání cache pomohlo, nevšiml jsem si, že cachuje anotace :)

Postman je boží (y)

Jinak přemýšlím, že to nakonec postavím na klasickém nette projektu nebo nevím v čem by mohl být rozdíl použití klasických presenterů oproti těmto controllerům ? Abstraktní třídy jsem zatím nepoužíval, tak jsem trochu zmatenej z implementování IPresnteru a funkce run.

Přihlašování uživatelů jako takové asi probíhat nebude, maximálně mi pošle credentials a já mu vrátím authToken vygenerovaný přímo pro toho jednoho usera.

Editoval kleinpetr (1. 9. 2017 13:46)

Pavel Janda
Člen | 977
+
0
-

@kleinpetr config.platform.php – to nevím, co je.

Rozdíl je v tom, že UI\Presenter ti řeší templating, kanonizaci url, signály, action/beforeRender/render/… Prostě tunu věcí, které API vůbec nemá co řešit. Pro lepší pochopení – mrkni se na UI\Presenter::run() :)

kleinpetr
Člen | 480
+
0
-

ten config.platform.php bylo nastavení v composer.json, už je to OK

jinak už chápu, ale úplně mi nedochází souvislost, třeba v tom LoginPresenteru máš nasměrovanou metodu GET na run(), která dostává Request, ale když si přidám POST = someMethod, tak se mi stejně při každym requestu zavolá metoda run()

a ještě se zeptám jak ideálně dosáhnout routování verzí v1,v2… ?

Editoval kleinpetr (1. 9. 2017 14:48)

Pavel Janda
Člen | 977
+
0
-

@kleinpetr V $request->getParameter('action') by ti teoreticky mohla přijít action..

Ideálně? :D Udělej si na to třeba v1 modul? Bůhví, těch způsobů je zase vícero. Modul podle verze je asi nejsnazší a nejrychlejší způsob.

kleinpetr
Člen | 480
+
0
-

@PavelJanda No počkat, to je ten projekt navržen tak, že jeden controller = jedna metoda (GET, POST, …)
snažím se vycházet nějak z dokumentace na ApiRouter, ale úplně se mi nedaří, vždy se volá metoda run()

No jasně modul je asi v pohodě, ale je tam nastavené nějaké přepisování názvu controlleru podle toho co je v anotaci v presenter=„…“ (jasně už mi to došlo – presenter=„V1:Presenter“)

Editoval kleinpetr (1. 9. 2017 16:58)

kleinpetr
Člen | 480
+
0
-

@PavelJanda
Tak jsem to nakonec nějak rozběhl na klasickém nette projektu.
Nicméně mám pár dotazů ohledně apiDocu

když zadám toto, přesně podle tvé dokumentace

/**
 * API Users
 *
 * @ApiRoute(
 *     "/api/v1/users[/<id>]",
 *     format="json",
 *     presenter="V1:Users",
 *     section="Users",
 *     priority=1
 * )
 */
class UsersPresenter extends BasePresenter
{

    /**
     * Get user detail
     *
     * @ApiRoute(
     *    "/api/v1/users/<id>",
     *    method="GET",
     *    example={
     *      "id": 110,
     *      "name": "John",
     *      "surname": "Doe",
     *      "email": "john@doe.cz",
     *      "createdDate": "2017-09-03 13:12:08",
     *    },
     *    tags={
     *        "secured": "#e74c3c"
     *    },
     *    response_codes={
     *    200="Success",
     *    400="Error in authentication process",
     *    401="Invalid authentication"
     *  }
     * )
     */
    public function actionRead($id)
    {
        if ($id) {
            $this->sendJson($this->apiResponseFormatter->formatMessage($id));
        }

        $this->sendJson($this->apiResponseFormatter->formatMessage('list'));
    }

    /**
     * Create user
     *
     * @ApiRoute(
     *    "/api/v1/users",
     *    method="POST",
     *    example={
     *      "id": 110,
     *      "name": "John",
     *      "surname": "Doe",
     *      "email": "john@doe.cz",
     *      "createdDate": "2017-09-03 13:12:08",
     *    },
     *    parameters={
     *     "email"={"type": "string"},
     *     "name"={"type": "string"},
     *     "surname"={"type": "string"}
     *    },
     *    tags={
     *        "secured": "#e74c3c"
     *    },
     *    response_codes={
     *    200="Success",
     *    400="Error in authentication process",
     *    401="Invalid authentication"
     *  }
     * )
     */
    public function actionCreate()
    {

    }

}
  1. když zadám pouze /api/v1/users – tak mi vyhodí no-route (když uzavřu <id> do []) tak mi docu ukáže dokumentaci jakobych měl zadané ID, což je špatně.
  2. Jak mohu přidat parametry pro POST, které chci zobrazit v dokumentaci ? když je přidám přes parameters, tak mi hodí, že je nemám uvedené v masce, což je jasný, protože je chci dostat POSTEM.
  3. Jak docílit toho, že když si zobrazím dokumentaci čistě pro GET api/v1/users (až bude fungovat) aby se mi zobrazilo, že jsou dostupné metody POST,PUT,DELETE atp, případně proklik na dokumentaci s parametrem apiRouteMethod
  4. Jak mohu nastavit dokumentaci pro GET api/v1/users (až bude funkční), tak jako ostatní metody, např. example apod ?

Díky moc :)

Editoval kleinpetr (1. 9. 2017 23:59)

Pavel Janda
Člen | 977
+
0
-

@kleinpetr Parametry: Jo to je jen k masce. Obecně se můžeš o jakýchkoliv parametrech rozepsat sám v dokumentaci. Například

/**
 * Example **query parameter** url (web):
 *
 * <json>
 * 	code: XCFeaKjj
 * </json>
 * **Response**
 * <json>
 * 	{
 *		"result": {
 *			"id" : 33
 *		},
 *		"status": "ok",
 *		"timestamp": 1473438980
 *	}
 * </json>
 *
 * @ApiRoute(
 * 	...
 * )
 */

Nevím přesně, co máš na mysli. Ale volil bych cestu zakázání generování dokumentace a za běhu na produkci a generoval jí staticky, viz docu – vygenerovaná dokumetace pak vypadá takto: https://ublaboo.org/client-api/

kleinpetr
Člen | 480
+
0
-

Ok, přijde mi to jako takové nepropracované do detailu, asi použiju apiary.io. Jinak routování se zdá, že funguje jak má.

Mockrát díky za pomoc, muselo to být vyčerpávající.

Petr