RESTful API dizajnerski savjeti iz iskustva

Vodič za savjete o dizajnu API-ja i procjene trendova.

© Nathaniel Merz - imaginarni vrhovi via. 500px
  • Prebacili su najnovije verzije ovog članka na moj GitHub!
  • https://github.com/ptboyer/restful-api-design-tips
  • Slobodno ga pročitajte tamo i ostavite zvijezdu ako ste uživali!
  • Ažurirano sredinom 9. lipnja 2019. godine.
Svi smo pripravnici u zanatu u kojem nitko nikada ne postaje majstor.

Dok ovo pišem, nasmijavam se kada vidim veliku paralelu iza sebe referencirajući Hemingwayev citat od nekoga drugog; čisto shvaćanje da mi se ne treba truditi u kreiranju drugačijeg provođenja odlomka sa sličnom funkcionalnošću za vrijednost rezultata (ili u ovom slučaju značenje) je književni testament ponovnog koda!

Ali nisam ovdje da pišem o prednostima kodnih paketa, već više da napomenem neke osobine koje sam cijenio i aktivno implementiram u sadašnje i buduće projekte. A od ovih značajki i detalja implementacije izrađujem vlastiti paket pravila i primitiva API-ja.

Od objavljivanja ovog članka, mnoge teme rasprava na kanalima poput Reddita pomogle su mi da prilagodim i prilagodim svoja objašnjenja i stavove o dizajnu API-ja. Želio bih zahvaliti svima koji su doprinijeli raspravi i nadam se da ovo pomaže u stvaranju ovog članka u vrijedniji resurs za druge! (Uređivanje: 9. lipnja / 2019.) I sada su prošle dvije godine od kada sam prvi put objavio ovaj članak i nevjerojatno je vidjeti da je pregledan 150.000+ puta i primio tisuće lajkova i dijeljenja, i još jednom želim izraziti svoju zahvalnost svim mojim čitateljima i sljedbenicima!

verzije

Ako ćete razviti API za bilo koju korisničku uslugu, trebali biste se pripremiti za eventualne promjene. To se najbolje ostvaruje pružanjem prostora s imenima verzija za vaš RESTful API.

To radimo jednostavnim dodavanjem verzije kao prefiksa svim URL-ovima.

DOBITE www.myservice.com/api/v1/posts

Međutim, kroz proučavanje drugih implementacija API-ja, postao sam poput kraćeg stila URL-a koji se nudi pristupanjem API-ju kao dijelu poddomene, a zatim izbacivanje / api iz rute; kraći i sažetiji je bolji.

DOBITE api.myservice.com/v1/posts

Međusobna podjela resursa (CORS)

Važno je uzeti u obzir da će prilikom postavljanja vašeg API-ja u drugu poddomenu, kao što je api.myservice.com, biti potreban implementacija CORS-a za pozadinu ako namjeravate ugostiti svoju sučelju na www.myservice.com i očekujete da koristite zahtjeve za dohvaćanje bez bacanje No Access-Control-Allow-Origin zaglavlje je prisutno grešaka.

rute

Prilikom izrade ruta morate krajnje točke razmišljati kao grupe resursa iz kojih možete čitati, dodavati, uređivati ​​i brisati, a ove radnje su u kapsuliranom obliku kao HTTP metode.

Koristite HTTP metode

Koristite metode kao što su:

  • GET za dohvaćanje podataka.
  • POST za dodavanje podataka.
  • PUT za ažuriranje podataka (kao cijelog objekta).
  • PATCH za ažuriranje podataka (s djelomičnim podacima za objekt).
  • IZbriši za brisanje podataka.

Želio bih dodati da mislim da je PATCH izvrstan način za smanjenje veličine zahtjeva za promjenu dijelova većih objekata, ali i da se dobro uklapa u uobičajena polja za automatsko slanje / automatsko spremanje.

Lijep primjer toga je Tumblrov zaslon "Postavke nadzorne ploče" na kojem se nekritične opcije o korisničkom iskustvu usluge mogu uređivati ​​i spremati, po stavci, bez potrebe za konačnim gumbom za podnošenje obrasca. To je jednostavno mnogo organskiji način za interakciju s korisnikovim preferencijalnim podacima.

Pojavi se oznaka "Spremljeno", a nestaje ubrzo nakon promjene opcije.

Upotrijebite množinu

To ima semantičkog smisla kada tražite / od mnogih postova / postova.

I za dobro, ne uzimajte u obzir / post / sve sa / post /: id!

// DO: množine su konzistentne i imaju smisla
GET / v1 / posts /: id / attachments /: id / comments

// NE: je li to samo jedan komentar? je li to oblik? itd
GET / v1 / post /: id / privitak /: id / komentar

U slučajevima poput ovih jednostavno se pokušajte što više približiti množini!

"Sviđa mi se ideja korištenja množine za nazive resursa, ali ponekad dobijete i imena koja se ne mogu množirati." (Izvor)

Koristite Nesting za filtriranje odnosa

Nizovi upita trebali bi se koristiti za daljnje filtriranje rezultata izvan početnog grupiranja logičkog skupa koje nudi odnos.

Cilj je dizajnirati staze krajnjih točaka koje izbjegavaju nepotrebne parametre niza upita, jer ih je općenito teže čitati i raditi s njima u usporedbi s stazama čija struktura potiče početno filtriranje i grupiranje takvih stavki na temelju odnosa dublje.

Ovaj / posts / x / privitak je bolji od / privitaka? PostId = x. A ovaj / posts / x / attachments / y / comments je mnogo bolji od / comments? PostId = x & attachmentId = y.

Upotrijebite više svog "rute-prostora"

Trebali biste nastojati održati svoj API što ravniji i ne gužvati svoje resurse. Dopustite sebi da pružite ravne rute za sve nadogradnje / brisanje svojih resursa, primjerice u slučaju postova koji imaju komentare, dopustite / postove /: id / komentare da biste dohvatili komentare za post na temelju odnosa, ali i ponudi / komentare /: id kako biste omogućili uređivanje komentara bez potrebe za obradom posta za svaki pojedinačni put.

  • Dulji putevi za stvaranje / dohvaćanje ugniježđenih resursa prema odnosima
  • Kraće staze za ažuriranje / brisanje resursa na temelju njihovog id-a.

Za filtriranje koristite kontekst autorizacije

Kad je u pitanju pružanje krajnje točke za pristup svim vlastitim izvorima korisnika (npr. Svim mojim vlastitim postovima), možda ćete naći na više načina za pružanje tih podataka, ovisi o vama koji najbolje odgovara vašoj aplikaciji.

  1. Nest / a odnos postova ispod / mene s GET / me / postovi ili
  2. Upotrijebite postojeću krajnju točku / postove, ali filtrirajte s nizom upita, GET / posts? User = ili
  3. Upotrijebite ponovo / postove za prikazivanje samo svojih postova i izlažite javne postove pomoću GET / feed / posts.

Koristite krajnju točku "Ja"

Neka krajnja točka poput GET / me dostavi osnovne podatke o korisniku kako se razlikuju kroz zaglavlje Autorizacije. To može uključivati ​​podatke o korisničkim dozvolama / opsezima / grupama / postovima / sesijama itd. Koji omogućuju klijentu da prikaže / sakrije elemente i rute na temelju vaših dozvola.

Kad je u pitanju pružanje krajnjih točaka za ažuriranje korisničkih postavki, dopustite PATCH / me da promijeni te intrinzične vrijednosti.

Unesite stranicu

Paginacija je jako važna jer ne želite da jednostavan zahtjev bude nevjerojatno skup ako postoje tisuće redaka rezultata. Čini se očiglednim, ali mnogi zanemaruju ovu funkcionalnost.

Postoji nekoliko načina da se to postigne:

Parametar "From"

Moguće je najjednostavnije implementirati, gdje API prihvaća parametar iz niza upita, a zatim vraća ograničeni broj rezultata iz tog pomaka (obično 20 rezultata).

Također je najbolje navesti granični parametar s tvrdom maksimumom, kao što je slučaj na Twitteru, s maksimumom od 1000 i zadanom granicom od 200.

Sljedeća stranica Token

Googleov API za Mjesta vraća odgovore u slijedeće stranice_token ako ima više informacija izvan ograničenih 20 rezultata po stranici. Zatim prihvaća pagetoken kao parametar novog zahtjeva koji nastavlja s vraćanjem više rezultata s novom next_page_token dok se ne iscrpi. Twitter čini sličnu stvar umjesto toga koristi paramu koja se zove next_cursor.

odgovori

Koristite koverte

"Ne volim ovijati podatke. Samo uvodi još jedan ključ za kretanje do potencijalno guste stabla podataka. Meta informacije trebaju ići u zaglavima. "
„Jedan argument za gniježđenje podataka je pružanje dva različita ključna ključa za uspjeh odgovora, * podaci * i * pogreška *. Međutim, tu razliku prenosim na HTTP statusne kodove u slučajevima grešaka. "

U početku sam držao stajalište da obloženi podaci nisu potrebni i da je HTTP sam po sebi pružio odgovarajuću „omotnicu“ za dostavu odgovora. No, nakon što pročitate odgovore na Redditu, mogu se pojaviti razne ranjivosti i potencijalni hakori ako ne zaklonite JSON matrice.

Trebate priložiti svoje podatke!

// DO: omotan
{
  podaci: [
    {...},
    {...},
    // ...
  ]
}

// NE omotaj
[
  {...},
  {...},
  // ...
]
"Pored toga, ako želite koristiti alat poput normalizr za raščlambu podataka s klijentove strane, uklanjanje omotnice uklanja potrebu za stalnim vađenjem podataka iz korisnog opterećenja odgovora kako bi se proslijedili normalizaciji."

Suprotno tome, pružanje dodatnog ključa za pristup vašim podacima omogućava pouzdanu provjeru je li nešto stvarno vraćeno, a ako ne, može se odnositi na tipku pogreške koja se ne sudara odvojeno od tijela odgovora.

Također je važno uzeti u obzir da će za razliku od nekih jezika jezici kao što je JavaScript procijeniti prazne objekte kao istinite! Stoga je važno da ne vratite prazan objekt radi pogreške kao dio odgovora u slučaju:

// omotnica, izdvajanje pogreške s korisnog opterećenja
const {podaci, greška} = opterećenje
// greške u obradi ako postoje
ako (pogreška) {bacite ...}
// inače
const normalizedData = normalizirati (podaci, shema)

JSON odgovori i zahtjevi

„Sve bi se trebalo pretvoriti u JSON. Ako očekujete JSON od poslužitelja, budite pristojni i pružite poslužitelju JSON. Dosljednost!"

Očito je "sve" precjenjivanje, kako neki komentari ističu, ali je bilo namijenjeno upućivanju na svaki jednostavan, običan objekt koji bi trebao biti serializiran u procesu konzumiranja i / ili povratka s API-ja.

Važno je definirati vrste medija putem zaglavlja u oba odgovora i zahtjeva za RESTful API. Kad se bavite JSON-om, osigurajte da uvrstite zaglavlje sadržaja / vrste json-a, odnosno za ostale vrste odgovora, bilo da su to CSV-ovi ili binarni podaci.

Vratite ažurirani objekt

Kada ažurirate bilo koji resurs putem PUT ili PATCH, dobra je praksa vratiti ažurirani resurs kao odgovor na uspješan POST, PUT ili PATCH zahtjev!

Koristite 204 za brisanje

Bilo je slučajeva u kojima se nisam uspio vratiti od uspjeha neke akcije (tj. DELETE), međutim osjećam da se vraćanje praznog objekta u nekim jezicima (kao što je Python) može ocijeniti kao lažno i možda nije tako očito na čovjekovo uklanjanje pogrešaka u njihovoj primjeni.

Podržite broj 204 - nema statusnog odgovora na sadržaj u slučajevima kada je zahtjev bio uspješan, ali nema sadržaja koji bi mogao vratiti. Koverta odgovora, zajedno s 2XX HTTP uspješnim kodom, dovoljna je da ukaže na uspješan odgovor bez proizvoljnih "informacija".

DELETE / v1 / posts /: id
// odgovor - 204
{
  "podaci": nula
}

Koristite HTTP statusne kodove i odgovore na pogreške

Budući da koristimo HTTP metode, trebali bismo koristiti HTTP statusne kodove. Iako je izazov ovdje odabrati poseban rez ovih kodova, a zatim ovisiti o podacima odgovora kako bi se detaljno opisale eventualne pogreške u odgovoru. Zadržavanje malog skupa kodova pomaže vam da konzumirate i postupate s pogreškama.

Volim koristiti:

za podatkovne pogreške

  • 400 za kada su tražene informacije nepotpune ili neispravne.
  • 422 za kada su tražene informacije u redu, ali nevaljane.
  • 404 jer kada je sve u redu, ali resurs ne postoji.
  • 409 kada postoji sukob podataka, čak i s valjanim informacijama.

za Auth pogreške

  • 401 za kada pristupni token nije naveden ili je nevažeći.
  • 403 za kada je token pristupa važeći, ali zahtijeva više privilegija.

za standardne statuse

  • 200 za kad je sve u redu.
  • 204 jer kada je sve u redu, ali nema sadržaja za povratak.
  • 500 za kada server baci pogrešku, potpuno neočekivano.

Nadalje, povratni odgovori nakon ovih pogrešaka također su vrlo važni. Želim razmotriti ne samo predstavljanje samog statusa, već i razlog koji stoji iza njega.

U slučaju pokušaja stvaranja novog računa, zamislite da pružamo e-poštu i zaporku. Naravno da bismo željeli da naša klijentska aplikacija spriječi bilo koji zahtjev s nevažećom e-poštom ili zaporkom koja je prekratka, ali stranci imaju toliki pristup API-ju kao i mi iz naše klijentske aplikacije kada je aktivan.

  • Ako nedostaje polje e-pošte, vratite 400.
  • Ako je polje lozinke prekratko, vratite 422.
  • Ako polje e-pošte nije valjana poruka e-pošte, vratite broj 422.
  • Ako je poruka e-pošte već preuzeta, vratite broj 409.
"Mnogo je bolje odrediti precizniji kôd serije 4xx nego običan 400. Razumijem da u odgovorno tijelo možete staviti sve što želite da razbije pogrešku, ali kodove je mnogo lakše čitati na prvi pogled." (Izvor)

Sada su iz ovih slučajeva dvije pogreške vratile 422 osobe, bez obzira na to zbog kojih su njihovih razloga različiti. Zbog toga nam je potreban kôd pogreške, a možda čak i opis pogreške. Važno je napraviti razliku između koda i opisa jer kôd namjeravam imati kao konstantu potrošnog stroja i poruke kao niza potrošnog materijala koji se može mijenjati.

U slučaju pogrešaka po polju, prisutnost polja kao ključa u pogrešci dovoljna je „koda“ koji pokazuje da je cilj pogreške u provjeri valjanosti.

Pogreške provjere polja

Za vraćanje tih pogrešaka po polju može se vratiti kao:

POST / v1 / register
// zahtjev
{
  "email": "end @@ user.comx"
  "lozinka": "abc"
}
// odgovor - 422
{
  "greška": {
    "kôd": "FIELDS_VALIDATION_ERROR",
    "message": "Jedno ili više polja podigli su pogreške u provjeri valjanosti."
    "polja": {
      "email": "Nevažeća adresa e-pošte.",
      "lozinka": "Lozinka je prekratka."
    }
  }
}

Pogreške operativne provjere

A za vraćanje operativnih pogrešaka provjere valjanosti:

POST / v1 / register
// zahtjev
{
  "email": "end@user.com",
  "lozinka": "lozinka"
}
// odgovor - 409
{
  "greška": {
    "code": "EMAIL_ALREADY_EXISTS",
    "message": "Račun s ovom e-porukom već postoji."
  }
}

Poruka može djelovati kao pomoćna poruka o pogrešci čitljivoj za ljude kako bi pomogla razumjeti zahtjev tijekom razvoja, a također u slučaju da se ne može koristiti odgovarajuća implementacija niza lokalizacijskog niza.

Na taj način vaša logika dohvaćanja promatra ne-200 pogrešaka, a zatim može izravnim putem provjeriti ključ pogreške iz odgovora i zatim ga usporediti s bilo kojom daljnjom logikom u aplikaciji klijenta.

Ovjera

Suvremeni, RESTful API-ji implementiraju provjeru autentičnosti s tokenima koji se najčešće pružaju putem zaglavlja autorizacije (ili čak parametra upita access_token).

Upotrijebite tokete sa sesijama koji se sami proširuju

U početku sam smatrao da je izdavanje JWT-ova za redovne API zahtjeve izvrstan način za bavljenje autentifikacijom - sve dok nisam htjela poništiti te tokene.

U svojoj posljednjoj reviziji ovog posta (a detaljno je u zasebnom postu) ponudio sam način da se JWT-ovi ponovo izdaju kroz dodatno pohranjenu tajnu klijenta "Refresh Token" (RT) koja je trebala biti zamijenjena za nove JWT-ove. Međutim, kako bi istekli ovi JWT-ovi, svaki je sadržavao referencu na RT koji je izdao, pa ukoliko je RT nevaljan / izbrisan, tako bi i JWT. Međutim, ovaj mehanizam pobjeđuje i apatridiju samog JWT-a ...

Moje rješenje sada je jednostavno korištenje a / session resursa krajnje točke za razmjenu vjerodajnica za prijavu za jedan jedinstveni token sesije (koristeći uuid4) koji je hashed i pohranjen kao redak baze podataka. Baš kao i mnoge moderne aplikacije, token se ne treba ponovno izdavati ako nema dugog razdoblja neaktivnosti (slično vremenskom prekidu sesije, ali na ljestvici od nekoliko tjedana). Nakon početne provjere autentičnosti, svaki budući zahtjev postepeno proširuje vijek trajanja tokena sve dok nije istekao.

Izrada sesije - prijava

Normalni postupak prijave izgledao bi kao:

  1. Primanje kombinacije e-pošte / zaporke s POST / sesijama, tretiranje sesija samo kao još jedan resurs.
  2. Provjerite e-poštu / hash-u protiv baze podataka.
  3. Napravite novi redak baze podataka sesije koji sadrži označen uuid4 () kao token.
  4. Vratite klijentu neshvaćeni tokenski niz.

Obnavljanje sjednice

U ovom protokolu, tokeni se ne moraju eksplicitno obnavljati ili ponovno izdavati. To je zato što API produžava životni vijek tokena ako i dalje vrijedi svaki zahtjev, što redovitim korisnicima omogućuje da ikada traje neka sesija.

Kad god API primi token, tj. Kroz zaglavlje autorizacije:

  1. Primite token, tj. Iz zaglavlja Autorizacija.
  2. Usporedite s hash-om tokena, ako nema odgovarajućeg retka sesije, otvorite pogrešku autentifikacije.
  3. Provjerite svojstvo updated_at sesije, ako se sada, ako je veća od ažurirane_at + session_life, sesija smatra isteklom, izbrišite redak sesije, otvorite pogrešku autentifikacije.
  4. Ako postoji i vrijedi od ažuriranog vremena, postavite vrijeme ažuriranog sata na sada () za obnavljanje tokena.

Upravljanje sesijama

Budući da se sve sesije prate kao redovi baze podataka preslikani na korisnika, korisnik može vidjeti sve njegove aktivne sesije slično prikazu sigurnosnih sjednica računa Facebooka. Možete odabrati i uključivanje pridruženih metapodataka koje ste odabrali pri početku izrade sesije, poput korisničkog agenta preglednika, IP adrese itd. I

Dohvaćanje svih vaših sesija je jednostavno kao:

  1. GET / sesije za vraćanje svih sesija povezanih s vašim korisnikom putem zaglavlja autorizacije.

Prekid sjednice - Odjava

Budući da imate seanse za svoje sesije, možete ih ukinuti da biste poništili neovlašteni ili neželjeni pristup vašem računu. A odjava bi jednostavno bila prekidanje sesije klijenta i čišćenje sesije od klijenta.

  1. Primite token kao dio DELETE / session / id zahtjeva.
  2. Usporedite s hash-om tokena, izbrišite odgovarajući redak sesije.

Izbjegavajte pravila za sastavljanje lozinke

Nakon mnogo istraživanja o pravilima zaporke, složio sam se da su pravila lozinki sranje i dio su NIST-ovih "ne", posebno imajući u vidu da pravila sastavljanja zaporki pomažu u sužavanju važećih lozinki na temelju njihovih pravila valjanosti.

Usporedio sam neke od najboljih točaka (s gornjih veza) za rukovanje lozinkom:

  1. Provjerite samo minimalnu duljinu zaporke unicode (min. 8–10).
  2. Provjerite da li postoje uobičajene lozinke ("password12345")
  3. Provjerite osnovnu entropiju (ne dopustite "aaaaaaaaaaaaaa").
  4. Ne koristite pravila za sastavljanje lozinke (barem jedno "! @ # $% &").
  5. Ne upotrebljavajte naputke za lozinku (rime s "assword").
  6. Nemojte koristiti provjeru autentičnosti utemeljene na znanju ("sigurnosna" pitanja).
  7. Nemojte isticati zaporke bez razloga.
  8. Nemojte koristiti SMS za dvofaktorsku provjeru autentičnosti.
  9. Upotrijebite sol za lozinku od 32 bita ili više.

Ovi "ne" trebaju znatno olakšati provjeru lozinke!

meta

Koristite krajnju točku "provjere zdravlja"

Kroz razvoj s AWS-om trebalo je načiniti jednostavan odgovor koji može pokazati da je instanca API-ja živa i da je ne treba ponovno pokretati. Korisno je i za jednostavno provjeravanje verzije API-ja na bilo kojem računalu u bilo kojem trenutku bez provjere autentičnosti.

GET / v1
// odgovor - 200
{
  "status": "trčanje",
  "verzija": "fdb1d5e"
}

Pružam status i verziju (koja se odnosi na git commit API-ja u vrijeme kada je izgrađen). Također je vrijedno spomenuti da ta vrijednost nije izvedena iz aktivnog .git repo-a koji se isporučuje s API-jevim spremnikom za EC2. Umjesto toga, ona se čita (i pohranjuje u memoriju) pri inicijalizaciji iz datoteke version.txt (koja se generira procesom sastavljanja) i podrazumijeva __UNKNOWN__ u slučaju pogreške u čitanju ili datoteka ne postoji.

Hvala na čitanju!

Ako ste uživali u mom članku i / ili smatrali korisnim, bilo bi mi drago ako ostavite klapu ili dvije ovdje na Mediumu, a moj članak stavite na GitHub ️.

Slobodno ostavite komentar u nastavku; razgovarajmo!