Nahrazení Databáze čtením dat z REST API

grovik
Člen | 53
+
0
-

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 | 762
+
-2
-

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 | 53
+
+1
-

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.

Kamil Valenta
Člen | 762
+
-3
-

Pak bych nevynalézal kolo a nasadil elasticsearch.

grovik
Člen | 53
+
0
-

Kamil Valenta napsal(a):

Pak bych nevynalézal kolo a nasadil elasticsearch.

Tady je to bohužel o tom, že kohouti jsou … atd. Takže jsem v pozici, kdy si prostě nevyberu.

Editoval grovik (9. 11. 2021 21:57)

Polki
Člen | 553
+
0
-

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 | 762
+
0
-

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 | 53
+
0
-

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
+
+2
-

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ě:

  1. Klient (browser, mobilní appka atp.) udělá požadavek na server.
  2. 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
  3. 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
+
+2
-

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 | 53
+
0
-

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
+
+1
-

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 | 53
+
0
-

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.

Polki
Člen | 553
+
+1
-

Však když bude potřeba, tak se ptej komunita je vstřícná a poradí. :)

grovik
Člen | 53
+
0
-

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 | 53
+
0
-

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
+
+2
-

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 | 53
+
+1
-

>

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 | 53
+
0
-

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
+
+1
-

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 | 53
+
0
-

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)

grovik
Člen | 53
+
+1
-

Tak jsem to nakonec prokopl.
Problém byl v tom že CURL do $response zapsal výsledek a data se vypsaly přímo do HTML.
Pomohlo doplnit parametr:

curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);