Nahrazení Databáze čtením dat z REST API
- grovik
- Člen | 76
Zdravím,
chystám se nahradit čtení dat z DB čtením dat z API.
V současnosti se data do výsledků vyhledávání a podobně tahají
z Postgre DB. Rád bych to nahradil čtením dat z API (REST API) které by
bylo jako nádstavba nad novým systémem tak aby stávající funkcionalita
byla zachována.
Stojím nad tím trošku v rozpacích a potřeboval bych pošťouchnout
správným směrem.
- Kamil Valenta
- Člen | 822
To je hodně široká otázka a asi se nikdo neodváží navrhnout řešení bez znalosti bližšího kontextu. Chceš nahradit docela rychlý spolehlivý resource (db) za dost možná pomalý a asi méně spolehlivý (REST API)? Většinou se dělá naopak replikace z API do db, abys měl data operativně po ruce. Už jen proto, že u vyhledávání můžeš využít indexy. U rest api budeš muset stáhnout vše a sekvenčně projít. Dost to hrozí slow downem systému, ale věštím z koule…
- grovik
- Člen | 76
Kamil Valenta napsal(a):
To je hodně široká otázka a asi se nikdo neodváží navrhnout řešení bez znalosti bližšího kontextu. Chceš nahradit docela rychlý spolehlivý resource (db) za dost možná pomalý a asi méně spolehlivý (REST API)? Většinou se dělá naopak replikace z API do db, abys měl data operativně po ruce. Už jen proto, že u vyhledávání můžeš využít indexy. U rest api budeš muset stáhnout vše a sekvenčně projít. Dost to hrozí slow downem systému, ale věštím z koule…
Tohle všechno si uvědomuji.
Jde o to, že původní aplikace čte nějaká data z DB, která bude v čase
X odstavena protože prostě už nebude platným zdrojem dat.
Je to poměrně specifická situace a proto se na to taky ptám, protože si
sám nejsem jistý jak se k tomu postavit tak aby se nad tím nestrávilo
mládí ;-).
Jde totiž i o to se následně vyhnout svázanosti s konkrétní DB protože
je ve finále jednodušší vytvořit to API, které bude potřebná data
předchroupávat a tahat z nově vznikajícího systému.
- Polki
- Člen | 553
Naopak to je dneska běžná věc, že se udělá aplikace, která zpřístupňuje data přes REST API, jelikož se s tím pak jednotně pracuje ve všech dalších systémech, jako třeba v Android appce, která se napřímo na SQL databázi napojit neumí. Líp se dělají pak pomocí API decentralizované databáze atd. atd.
Elastic se zase používá pro fulltext vyhledávání pro co byl navržen, takže to je komplet mimo mísu.
V rámci API a databáze vzniká jediný problém a to pomalejší čtení z API a to proto, že když se komunikuje s databází napřímo, tak tam není ten mezičlánek API, které řídí nějaká appka. Otázka je, jakým způsobem to API uchovává data. Pokud jde třeba o to, že API vnitřně data ukládá do RAMky, tak ta rychlost je v určitých případech s klasickými databázemi srovnatelná, ne-li někdy rychlejší, jelikož si algoritmus manipulace s daty navrhneš konkrétně na určitou operaci.
Otázka se tedy celá točí kolem implementace toho té aplikace, která má zprostředkovávat to API.
No a i kdyby to bylo pomalejší, tak je to ve výsledku úplně jedno, jelikož se běžně v aplikacích všechny dotazy do databází cachujou, takže jeden dotaz po expiraci cache je prd.
No a pokud chceš aby tě někdo nakopl informací jak v něčem dál, tak
je potřeba specifikovat, co přesně potřebuješ udělat.
Máš za úkol vytvořit tu aplikaci, co bude nabízet to API, nebo to API
dostaneš a máš jen na něj napojit aktuální aplikaci? Nebo máš
v aplikaci udělat to, že máš překopírovat aktuální data z Postgresu do
toho API?
Zkus přesně popsat problém a dostaneš přesnou odpověď :)
- Kamil Valenta
- Člen | 822
grovik napsal(a):
V současnosti se data do výsledků vyhledávání
Polki napsal(a):
Elastic se zase používá pro fulltext vyhledávání pro co byl navržen, takže to je komplet mimo mísu.
Tak to pak jo :)
- grovik
- Člen | 76
Polki napsal(a):
Naopak to je dneska běžná věc, že se udělá aplikace, která zpřístupňuje data přes REST API, jelikož se s tím pak jednotně pracuje ve všech dalších systémech, jako třeba v Android appce, která se napřímo na SQL databázi napojit neumí. Líp se dělají pak pomocí API decentralizované databáze atd. atd.
Elastic se zase používá pro fulltext vyhledávání pro co byl navržen, takže to je komplet mimo mísu.
V rámci API a databáze vzniká jediný problém a to pomalejší čtení z API a to proto, že když se komunikuje s databází napřímo, tak tam není ten mezičlánek API, které řídí nějaká appka. Otázka je, jakým způsobem to API uchovává data. Pokud jde třeba o to, že API vnitřně data ukládá do RAMky, tak ta rychlost je v určitých případech s klasickými databázemi srovnatelná, ne-li někdy rychlejší, jelikož si algoritmus manipulace s daty navrhneš konkrétně na určitou operaci.
Otázka se tedy celá točí kolem implementace toho té aplikace, která má zprostředkovávat to API.
No a i kdyby to bylo pomalejší, tak je to ve výsledku úplně jedno, jelikož se běžně v aplikacích všechny dotazy do databází cachujou, takže jeden dotaz po expiraci cache je prd.
No a pokud chceš aby tě někdo nakopl informací jak v něčem dál, tak je potřeba specifikovat, co přesně potřebuješ udělat.
Máš za úkol vytvořit tu aplikaci, co bude nabízet to API, nebo to API dostaneš a máš jen na něj napojit aktuální aplikaci? Nebo máš v aplikaci udělat to, že máš překopírovat aktuální data z Postgresu do toho API?
Zkus přesně popsat problém a dostaneš přesnou odpověď :)
Je to tak, že můžu ovlivnit to API ve stylu, řeknu kolegovi jaká data
budu potřebovat.
Můj úkol je původně DB aplikaci upravit tak aby místo té DB používala to
API.
Tzn. původní DB nahradit tím API. Rychlost asi není tak podstatná nejedná
se tam primárně o velké množství dat. Jsou to spíš jednotky položek,
REST API tam už funguje, ale jak jsem psal mám možnost ovlivnit co dostanu
zpět, potažmo na jaký dotaz dostanu jaká data.
- Polki
- Člen | 553
Kamil Valenta napsal(a):
Tak to pak jo :)
Promiň, přesně tohoto jsem si nevšiml, takže se omlouvám za moje mimo. :D Nicméně I tak to nemusí stačit, jelikož:
grovik napsal(a):
V současnosti se data do výsledků vyhledávání
Otázka je, co se vyhledává a jak se vyhledává. Ne všechno vyhledávání je FullText
Kamil Valenta napsal(a):
Už jen proto, že u vyhledávání můžeš využít indexy. U rest api budeš muset stáhnout vše a sekvenčně projít.
Indexy ti jsou u FullTextu stejně k ničemu a tedy se to bude vnitřně sekvenčně procházet. Ušetří se ale čas stahování dat to je pravda.
Kamil Valenta napsal(a):
Pak bych nevynalézal kolo a nasadil elasticsearch.
grovik napsal(a):
V současnosti se data do výsledků vyhledávání a podobně tahají z Postgre DB.
Kdo ví co to a podobně je? Klidně může jít o filtr podle roku a
k tomu se už Elastic tolik nehodí.
Pokud jde o časté vyhledávání FullTextem, tak si myslím není problém
udělat spojení. V několika firmách, se kterými spolupracuji je postup
takový, že aplikace fungují následovně:
- Klient (browser, mobilní appka atp.) udělá požadavek na server.
- Server (Nette) resolvne požadavek a zjistí, jestli je třeba načítat data z databáze. Pokud ano, podívá se, jestli data nemá v cachi. Pokud má, tak je vrátí, pokud ne, udělá požadavek na API
- API (Microservice v Nette/C++/go whatever) opět resolvne požadavek. Mrkne, jestli má data v cachi a pokud ano, tak je vrátí pokud ne, udělá dotaz na data do databáze, který probíhá následovně:
- Pokud šlo o požadavek, který evokuje FullText vyhledávání, udělá se dotaz na ElasticSearch, který rychle vrátí výsledek. Tento výsledek obsahuje index z PK sloupce třeba z Postgresu
- Pomocí klíče, který byl vrácený z Elasticu se udělá dotaz na konkrétní řádky do Postgresu
- Výsledek se zacachuje a vrátí Serveru
- Pokud šlo o požadavek, který nepotřebuje FullText vyhledávání (získání podle indexu atp.), tak se udělá přímo požadavek do Postgresu, výsledek se zacachuje a odešle zpět na Server
Při přidávání/editaci to funguje dost podobně s tím, že se nejdříve udělá insert/update do Postgresu a poté se sloupce, které podporují FullText vyhledávání uloží/aktualizují do Elasticu. Nově zapsaná data se opět zacachují atd…
- Polki
- Člen | 553
grovik napsal(a):
Je to tak, že můžu ovlivnit to API ve stylu, řeknu kolegovi jaká data budu potřebovat.
Můj úkol je původně DB aplikaci upravit tak aby místo té DB používala to API.
Tzn. původní DB nahradit tím API. Rychlost asi není tak podstatná nejedná se tam primárně o velké množství dat. Jsou to spíš jednotky položek, REST API tam už funguje, ale jak jsem psal mám možnost ovlivnit co dostanu zpět, potažmo na jaký dotaz dostanu jaká data.
No tak to by pak nemělo být těžké. Záleží, jak je ta původní ‚DB aplikace‘ navržená, ale pokud má odstíněnou DB vrstvu nějakým šikovným adapterem/fasádou/proxy v modelu (což jsem tu na fóru zmiňoval několikrát a vždy mě lidi zlinčovali :D ), tak by neměl být problém a jen vyměníš ty třídy, co se připojovaly k Postgresu za třídy, které se budou nově připojovat k REST API a zbytek aplikace zůstane nezměněný.
Pokud to tak nemáš, doporučuju si nejdřív veškerou práci s databází vyextrahovat do zvláštní sekce v aplikaci (Většinou tzv. Repozitáře) která ty data zpřístupní, takže aplikace se bude například na data o uživateli ptát třídy UserRepository a jestli ty data ta třída UserRepository vezme z Postgresu, nebo z nějakého API je zbytku aplikace úplně jedno.
Příklad by mohl být nějak takto:
Původní třída:
class ArticleRepository
{
public function search(string $searchText): array
{
return $this->database->table('article')
->where('content LIKE ?', '%' . $searchText . '%')
->fetchAll(); // Případně konverze na nějaké třídy
}
}
Nová třída
class ArticleRepository
{
public function search(string $searchText): array
{
return $this->makeCurlRequest(
url: '/articles',
method: 'GET',
data: [
'content' => $searchText,
],
); // Případně konverze na nějaké třídy
}
}
A všude v aplikaci budeš mít jednoduše volání
$articles = $this->articleRepository->search($searchText);
Právě kvůli těmto případům, že se může stát, což se dneska stává častěji, než si člověk může myslet, že se změní databáze, by se takto měly aplikace psát a to tak, že volání jakékoliv jiné služby/využívání knihovny, kterou si nepíšu sám by mělo být podmíněno nějakým adapterem/proxy atp., aby práce s tou danou třídou/knihovnou/microservicou byla od aplikace odstíněná a dalo se to jednoduše kdykoliv nahradit za jinou.
- grovik
- Člen | 76
Polki napsal(a):
Tohle vypadá dobře.
Tam je nejhorší, že ta aplikace se psala třeba posledních 10 let a
postupně se různě nabalovala a občas tam jsou fakt divné věci tak různě
spojené.
Já to postupně procházím a občas narazím na něco co se musí předělat a
teď je to spojené s tím, že se mění struktura celé té aplikace tak
vznikají různé situace.
Například jsem v PostgreDB našel linky na jinou MySQL DB z které se skrz
tu Postgre Tahají data :D.
- Polki
- Člen | 553
Fíjo. Tak to je mazec.
Když dostanu zakázku, kterou dělal nějaký nováček, nezkušený, nebo se
na tom vystřídalo více lidí a každý si to dělal po svém, tak už
postupuji jen tak, že doporučím komplet přepis a pokud zákazník
nesouhlasí, tak to šupnu nějakému žákovi/méně zkušenému kolegovi, aby
se na tom naučil aspoň.
Chápu ale, že se takto jednoduše nejde zbavit problému vždy :D
No já doporučuji jak jsem psal extrakci práce s databází do
samostatných tříd, díky čemuž pak napojení na API bude hračka.
Možná ještě někdo něco poradí a když ne, tak přeji hodně pevné nervy
a trpělivosti :)
- grovik
- Člen | 76
Polki napsal(a):
Fíjo. Tak to je mazec.
Když dostanu zakázku, kterou dělal nějaký nováček, nezkušený, nebo se na tom vystřídalo více lidí a každý si to dělal po svém, tak už postupuji jen tak, že doporučím komplet přepis a pokud zákazník nesouhlasí, tak to šupnu nějakému žákovi/méně zkušenému kolegovi, aby se na tom naučil aspoň.Chápu ale, že se takto jednoduše nejde zbavit problému vždy :D
No já doporučuji jak jsem psal extrakci práce s databází do samostatných tříd, díky čemuž pak napojení na API bude hračka.
Možná ještě někdo něco poradí a když ne, tak přeji hodně pevné nervy a trpělivosti :)
Tak to díky :D!
Hele já jsem v Nette začátečník vlastně se to učím. Takže asi docela
dobrý postup. Ale na druhou stranu neznám všechny finesy a hlavně možnosti
a to je to co mě obecně trápí.
Pořád narážím na to, že když někdo něco vysvětlí na příkladu je to
cajk, ale samotná dokumentace je dost nepřehledná v tom, že vlastně není
jasné k čemu co slouží jen je napsaná udělej tohle a pak se stane tohle.
:D
Takže tam mi to drhne.
- grovik
- Člen | 76
Tak došlo na lámání chleba :D.
API je v nějaké funknčí verzi a teď k němu makám na tom klientovi.
Vzhledem k tomu, že v aplikaci už je RestAPI pro FlexiBee, která znám
poměrně dobře, napadlo mě odpíchnout se od toho. Takže do konfigu jsem si
přidal další položku s odkazem na to API.
Jednodušší to bude v první řadě v tom, že tam se nebudou dělat zápisy
ale jen se budou číst data.
Takže teď do toho říznu ;-).
1. Jak to rozložit aby to bylo dobré do budoucna. V modelu je metoda co
tahá data z DB jmenuje se getClientData a používá $id jako to podle čeho
se hledá a vypadne z ní všechno co je potřeba.
Já se pokusím na první dobrou použít co tu psal @Polki to se mi
jeví jako moc pěkné řešení. Hodím ho do modelu.
2. Otázkou jak moc to ovlivní presenter, ale já se pokusím to udělat tak aby to fungovalo ideálně stejně prostě podstrčím stejná data získané jinou cestou.
3. Držte mi palce :D.
- grovik
- Člen | 76
Tak jo vrtám se v tom celý den a mám tu jeden příklad za všechny
:-).
Původní metoda:
public function getLocalities()
{
if (!isset($this->localities)) {
$sql = "
SELECT DISTINCT o.id, o.obec
FROM modemy AS v
JOIN obec AS o ON o.obec=v.obec
ORDER BY o.obec
";
$this->localities = $this->db->query($sql)->fetchPairs('id', 'obec');
}
return $this->localities;
}
REST API má v podstatě dovede vrátit to stejné:
curl -X 'GET' \
'/api/v1/admin/locality' \
-H 'accept: application/json' \
-H 'Authorization: Basic YWRtaW46eGVQOHphaDk='
Teď otázka zní, jak nejlépe ten vrácen JSON, Příklad níže, dostat do stejné podoby a tu vrátit.
JSON příklad:
[
{
"localitytId": 1,
"cfgCode": "velka_bites",
"name": "Velká Bíteš",
"cmtsConfigurationId": null,
"created": "2021-10-02T09:50:21.212453+02:00"
},
{
"localitytId": 2,
"cfgCode": "hustopece",
"name": "Hustopeče",
"cmtsConfigurationId": 6,
"created": "2021-10-02T09:50:21.212453+02:00"
}
]
Editoval grovik (1. 12. 2021 15:51)
- Polki
- Člen | 553
Jestli budeš chtít vracet jen ID a Obec, tak bych možná zvážil, jestli by API nemohlo jen tyto 2 prvky vracet. Ušetřil by si přenos dat po síti a výsledná funkce by vypadala takto asi:
public function getLocalities()
{
if (!isset($this->localities)) {
$response = /* Tady udělám CURL request */;
$this->localities = Json::decode($response);
}
return $this->localities;
}
:)
Pokud budeš potřebovat i ostatní prvky, tak můžeš zkusit něco takovýho:
public function getLocalities()
{
if (!isset($this->localities)) {
$response = /* Tady udělám CURL request */;
$localities = Json::decode($response);
$this->localities = [];
foreach ($localities as $locality) {
$this->localities[$locality->localitytId] = $locality->name;
}
}
return $this->localities;
}
Nebo tak :)
PS: Promiň, že ti odpovídám až teď :D Spal jsem po 37 hodinách práce :D
Editoval Polki (1. 12. 2021 17:27)
- grovik
- Člen | 76
>
Nebo tak :)
PS: Promiň, že ti odpovídám až teď :D Spal jsem po 37 hodinách práce :D
@Polki Proboha jen se neomlouvej, to já jsem vděčný za kopnutí
správným směrem!!
Vypadá to velice elegantně to by mohlo dobře fungovat, ono tam bude víc
takových míst tady se jedná o takové malé nasazení.
Jinak těch dat je v tomhle případě celkem dost, ale není to krytické. Tu
funkcionalitu použe někdo jednou za hodinu a je celkem fuk jestli bude čekat
10 vteřin nebo 15. Takže tady asi i pro mě logickou optimalizaci
neprotlačím (není to tak podstatné).
Ještě jednou díky. Zkusím sem pak dát nějak použitý výsledek aby to
tu mělo finální řešení :-). Protože to je myslím moc důležití.
Díky!
- grovik
- Člen | 76
Tak jsem to aplikoval a API klient funguje. Problém nastal se zpracováním,
protože podle to vypadá že JSON::DECODE to rovnou vysype do výstupu.
Aspoň tak to vypadá když se na to člověk koukne. Následně to pak logicky
při parsování foreach hodí chybu. Nemůžu přájít na to proč se to tak
stane. Nemá někdo tušení? :-)
- Polki
- Člen | 553
Json::decode()
určitě ve výchozím stavu nic na žádný
výstup nesype.
Toto dekódování jsonu dělá věci úplně stejně jako JavaScript, tedy
to, co je v [] závorkách vezme jako pole a to, co je v {} závorkách vezme
jako objekt a tedy jako stdClass. Alternativně můžeš vynutit, aby vše bylo
pole/asociativní pole a to druhým parametrem
Json::FORCE_ARRAY
.
Takže ten JSON, který jsi ty poslal v příkladu se díky
Json::decode()
přeloží do tohoto:
array
0 => stdClass
localitytId: 1
cfgCode: 'velka_bites'
name: 'Velká Bíteš'
cmtsConfigurationId: null
created: '2021-10-02T09:50:21.212453+02:00'
1 => stdClass
localitytId: 2
cfgCode: 'hustopece'
name: 'Hustopeče'
cmtsConfigurationId: 6
created: '2021-10-02T09:50:21.212453+02:00'
což je iterovatelný a ten můj úryvek kódu bude fungovat.
Takže chyba bude nejspíš někde jinde.
- grovik
- Člen | 76
Pokud tam iterace je Laděnka vyhodí chybu s tím, že foreach má špatné parametry (logicky ta první proměnná není pole ani objekt). Pokud iteraci vyhodím nebo se podívám přímo do zdrojáku stránky, který se zobrazil, tak je hned na začátku dřív než začne vlastní HTML výstup, vyklopený ten JSON.
Jak jsem to teď napsal tak mě napadlo jestli to nevypadne někde z CURL_EXEC. Nemáš tušení jestli by se to nemohlo třeba při chybě chovat takhle? Nikde jsem k tomu nenašel bližší informace.
Ještě dotaz mimo hru: všiml jsem si že v config.neon jsou nějaké parametry například pro DB atd. Jestli by se nedalo parametry pro ten CURL, potažmo pro to napojení na API přesunout tam a volat to tak aby se nemuseli všude doplňovat přímo ty údaje do kódu (aby nastavení bylo na jednom místě).
Editoval grovik (6. 12. 2021 14:04)