» »

Nasveti glede API-ja

Nasveti glede API-ja

i33a ::

Pozdravljeni,
trenutno izdelujem nek API in potrebujem nekaj pomoči/priporočil glede njegove izdelave.
Največja težava, ki jo imam je naslednja:
- API posreduje neke podatke, ki jih kdaj imam v svoji bazi, v večini primerov pa moram klicati še nek tuj API in pridobljene podatke združiti, obdelati,.. kar traja nekaj minut.
Predstavljam si, da moj primer ni osamljen in se je že kdo srečal z njim / obstaja najboljša praksa v takem primeru.
Naj uporabniku v primeru, da potrebujem čas za izdelavo odgovora (več minut) enostavno odgovorim naj pošlje poizvedbo kasneje? Najbrž nima smisla, da bi toliko časa le čakal na moj odgovor.

Povezano s to težavo je tudi notranje delovanje pridobivanja. Trenurno imam v mislih idejo, da bi vsak request zabeležil, pogledal, če imam na voljo odgovor ter si podatke, ki jih moram pridobiti beležil v posebno tabelo v bazi. Nato pa spisal nek daemon, ki bi te podatke pridobival in shranjeval, ter popravil status v podatkovni bazi. Predlagate kakšno boljšo alternativo?

Ker gre za večje število datotek jih najbrž ni najbolj pametno hraniti v enem samem direktoriju. Mi lahko daste kakšen namig kako jih hraniti?

Najlepša hvala za nasvete.

neverlucky ::

Do kaksne mere pa potrebujes podatke z drugega apija? Bi bilo mozno, da bi stvari vnaprej poracunal in jih nato serviral?

Ce pa to ne gre, pa da se klici ponavljajo, pa vsaj naredi da ti cache podatke, da je le prvi klic dolgotrajen. Sicer pa je nekaj minut za izracun kar veliko.

AndrejS ::

Kaj sedaj deluje počasi ? Externi API , ali združevanje podatkov ?

genesiss ::

Lako API dizajniraš tako, da ti client poda callback url, kamor sporočiš da se je operacija zaključila. Tako client dobi response takoj, ti pa čisto nedovisno pohandlaš request in komunkacijo z eksternim sistemom, ter odgovor javiš ko je na voljo.

Zgodovina sprememb…

  • spremenil: genesiss ()

Looooooka ::

Mi večinoma delamo na ravno takih zadevah. Kup bančnih/plačilnih sistemov, ki lahko odgovorijo takoj ali pa ne. Pri klicih, kjer se zaradi zunanjih težav ne da garantirati odgovora v določenem času, vrnemo po n sekundah odgovor s statusom, da se zadeva procesira.
Klient lahko potem zahteva status ali pa pri zadevah, ki se jih, da preklicati zahteva preklic. Tudi tam je spet ista logika.
Za API-je, kjer hočemo biti čim bolj fleksibilni in se smatra, da ne bo nekega resnega bremzanja sistema pa imamo spet opcijo, da si stranke nastavijo urlje na naši strani(ali pa jih pošljejo ob zahtevku) na katere jim potem pošljemo odgovor, ko se zadeve sprocesirajo.
Rešitev je lahko torej eno, drugo ali pa oboje.
Za manj dela je seveda tista, kjer odgovornost za preverjanje statusa pade na stran klienta, še najboljša :)

HotBurek ::

Rešiš lahko tako.

1 - Client naredi request na tvoj API
GET server.cum/givemedata
2 - Server generira session-id (npr. datum+čas+random kot 201907251448123456) in ga vrne klientu
RESPONSE plain/text session-id=201907251448123456
3 - Klient na ~5 sekund dela POST request, v katerem je session-id
POST server.cum/status
4 - Server vrne [status]=running, ali pa [status]=done
RESPONSE plain/text [status]=running
5 - Ko je done, client naredi request za poizvedbo podatkov
POST server.cum/getdata

Ko klient naredi prvi request na /getdata, generiraš ID, zaženeš nov proces (asinhrono), in "hitro" vrneš ID klientu (ki ne čaka 5 minut na response.) Nov, ločen proces potem, ko konča, spremeni status za določen request.


Primer, kako v Pythonu odpređ nov proces:
import random;
import subprocess;

# path
pathlog = "/path/to/log";

# id
fileid = "2019" + "1500" + str(random.randint(1000000000, 2000000000);


# GET call from client
# read input data ...

# create long running async process
DEVNULL = open(os.devnull, "w");
callproc = subprocess.Popen(["python3.7", "/path/to/asyncdataprocessscript.py", "path=" + str(pathlog), "fid=" + str(fileid)], shell=False, stdout=DEVNULL);

# return id to http client
print("fileid");



V drugi skripti pa se parametre prebere tako:
# define
path = "";
fid = "";

# read file id
for i in range(len(sys.argv)):
    arg_temp = sys.argv[i];
    if arg_temp.startswith("path="):
        path = arg_temp.replace("path=", "", 1);
    elif arg_temp.startswith("fid="):
        fid = arg_temp.replace("fid=", "", 1);
root@debian:/# iptraf-ng
fatal: This program requires a screen size of at least 80 columns by 24 lines
Please resize your window

nightrage ::

HotBurek ti je dal lepo rešitev, ena izmed rešitev je tudi duplex spletna storitev, če npr. delaš v C#, ki lahko vrnejo zahtevo glede na callback ko server sprocesira podatke. Rešitev HotBurek-a je boljša.

Zgodovina sprememb…

i33a ::

Zunanji podatki iz drugega API-ja so nujni in jih ne moremo predviditi (kdaj gre za več klicev, ki so med sabo odvisni - rezultat prejšnjega določa klic naslednjega). Količina podatkov je precej velika, tako da celote ne moremo prenesti s tistega API-ja. Počasen je externi API & tudi ni najboljše zasnovan, ampak v kratkem se na tem področju ne bo nič spremenilo.

Hvala vsem za odgovore. Rešitev, ki jo je podal HotBurek mi je zelo všeč in se bom najbrž kar lotil njene implementacije.

Imate kakšno mnenje o mojem drugem vprašanju glede samega pridobivanja podatkov? (Zapis v bazo, ločena skripta, ki v bazi pregleduje katere zahteve so bile pridobljene).

Spura ::

1. Ce ti ni problem stevilo connectionov, oziroma timeouti na proxyih, potem ne vidim razloga zakaj bi kompliciral s nekimi pollingi in podobno.
2. Ce imas na guiju blocking flow potem ti itak nic ne koristi to da preverjas status.

Pac zakljuc response ko bo bodo podatki na voljo, pa da ne pozres celga threadpoola na serverju lahko naredis async vse skupaj.

Od Hotbureka resitev je pa cista komplikacija. Ta nek session id nima veze s taskom ki se zadi opravlja, tko da ko se task zakljuci nimas pojma za katere session idje je done, razen ce si bos v bazo zapisoval tudi session ID pa za kateri fetch se gre. Bedarija.

Zgodovina sprememb…

  • spremenil: Spura ()

youPlonker ::

Pohvalil bi avtorja in sodelujoce v tej temi za zanimive odgovore. Resitev HotBureka s sejnimi id-ji se mi ne zdi ravno lepa, ker vodenje seje predstavlja dodaten napor za server. Callback url resitev se mi zdi najbolj preprosta in to the point, vendar v tem primeru mora pac klient biti ze sam tudi streznik, ne samo odjemalec...

Osebno, kot idealisticni nodejs developer :), bi to resil tako:

1. Imam HTTP streznik, ki klientu s podatki odgovarja na zahteve. V primeru, ko podatke v svoji DB imam, vrnem 200 OK s podatki. V primeru, ko jih nimam in bo potrebno klicanje zunanjih APIjev ter dolgo procesiranje, pa vrnem 202 Accepted brez podatkov in hkrati posljem sporocilo v nek deljeni message queue, o tem, da mora tisti, ki bo prevzel sporocilo klicat zunanji API in procesirat. S klientovega gledisca, on dokler podatkov ni, poola z nekim timeoutom npr. 10 sec in pac dobiva 202-ke, sele ko so podatki pripravljeni (v DB), lahko server vrne 200.

2. Dodaten locen proces (npr. X) pobere sporocilo iz cakalne vrste in poklice zunanje APIje ter zacne procesiranje. Samo X lahko klice zunanji API. Za primer, ko klient od streznika nekajkrat zapored zahteva isto vsebino (na katero dobi 202) in se v cakalno vrsto nabijajo sporocila istega taska, vodi X neko svojo evidenco, o tem, kaj je ze v teku in kaj ne (da obdekuje le unique sporocila). Za prvo silo je lahko to cisto navadna key value spremenljivka, samo da ne gres isto stvar delat vec kot enkrat. Ravno tako ima X na voljo nek cache (npr. Redis), v katerega tlaci celotne odgovore od zunanjih APIjev za prihodnje taske, ki bodo mogoce potrebovali kej podobnega... Rezultat procesiranja zapises v DB, do katere ima dostop tvoj HTTP streznik.

Meni osebno je lepsi pristop z dvema locenima procesoma, ker lahko delujeta neodvisno en od drugega in moj HTTP API ne postane predebel. Ravno tako je to primerna resitev, ce v prihodnosti zelis podatke vnaprej pripravljati (torej ne samo kot odziv na klientov request, ampak npr. pozenes X vsak dan ob neki uri za najbolj uporabljane vsebine, da s tem za vecino uporabnikov tvojega APIja pohitris izkusnjo).

HotBurek ::

Za caching lahko imaš tako, da vedno prvo pogledaš v bazo, in če tam ni podatkov, narediš klic na zunanji API.

Bi bilo pa dobro, da vsaj malo bolj podrobno napišeš o teh podatkih.


Recimo en primer, da je API klic tole ( https://www.skb.si/sl/tecajnice/tecajni... ), podatki pa so tabela s tečaji.

- Prvi request ne najde vsebine v lokalni bazi, zato zaženeš proces, ki naredi request na externi API in shrani podatke v bazo. Medtem clientu vrneš response, da podatki trenutno niso na voljo in naj preko session-id za ta request preverja, kdaj bodo podatki na voljo.
- Ko proces shrani podatke v lokalno bazo, se zraven zapiše podatek, kdaj so podatki bili shranjeni. (Jst uporabljam format datum-čas 20190726-152407).
- Pri vsakem naslednjem client requestu preveriš, če je podatek v lokalni bazi in ker je, vrneš tega (skupaj s podatkom, kdaj so bili podatki pridobljeni oz. koliko so stari.) Pri branju iz lokalne baze poleg podatkov prebereš tudi datum-čas, ter narediš primerjavo, koliko časa je preteklo od zadnje posodobitve. Jst sem imel primer, kjer sem max no-cache imel 10 minut. Torej, po prvem refreshu vsi naslednji berejo iz lokalnega cache-a, dokler je le-ta mlajši od 10 minut.
Ko pride prvi clien request in je cache starejši od 10 minut, klientu še kar vrneš stare podatke, hkrati pa zaženeš nov proces in narediš poizvedbo na zunanji API ter osvežiš podatke iz lokalne baze.

Ta stvar deluje, če imaš pogoste requeste za iste podatke. Npr. če je za isti API request (oz. podatke) vsako minuto en client request, po zgornji formuli podatki nikoli ne bodo starejši od 11 minut. Problem bi bil, če bi naredil en request ob 11tih dopoldan, naslednji pa ob 17 popoldan. Ta, ta drug request, bi kr iz prve dobil podatke od 11tih iz cache-a. Novi podatki bi bili na voljo šele od 17:05, če predvidevamo, da API request traja 5 minut...


Če je podatkov več, kot prostora na disku, lahko asinhrono zaganjaš skripto, ki ti prebere timestamp vseh "podatkov", jih sortira po starosti, in pobriše najstarejše. Recimo, če imaš 1GB prostora, en API podatek zasede 1MB, si narediš skritpo, ki poosrtira (od najmlajši do najstarejši) in pobriše vse, ki so višje od 800 mesta.

Če so podatki števila, jih shrani v SQL bazo (npr. MariaDB), če so slike/pdf, pa v NoSQL (npr. MongoDB).
root@debian:/# iptraf-ng
fatal: This program requires a screen size of at least 80 columns by 24 lines
Please resize your window

Arey ::

Kaj pa če bi naredil vse skupaj prek websocketov? Client zahteva podatke prek websocketa (ali pa navadnega HTTP requesta, prek katerega dobi id za websocket), ko server zmelje svoje stvari clientu pošlje podatke prek websocketa.


Vredno ogleda ...

TemaSporočilaOglediZadnje sporočilo
TemaSporočilaOglediZadnje sporočilo
»

JS frameworks: knockoutJS, angularJS, react, ...

Oddelek: Programiranje
417273 (4118) kuall
»

Optimizacija API-ja

Oddelek: Programiranje
161881 (1114) AndrejS
»

Prenašanje cookijev med poddomenami

Oddelek: Informacijska varnost
201844 (1559) jype
»

[baze] Povezava do slike ali BLOB?

Oddelek: Programiranje
101658 (1455) BlueRunner
»

tomcat - problem z encodingom

Oddelek: Programiranje
72345 (2259) kopernik

Več podobnih tem