title |
---|
Fyrirlestur 5.3 — Vefþjónustur |
Ólafur Sverrir Kjartansson, osk@hi.is
- Töluvert ólíkt því að hanna vefi með útliti
- Neytendur okkar eru aðrir forritarar og þeirra forrit
- Mun minna rými til að túlka eitthvað eins og villu á vef
- Getum sparað öðrum ótrúlega mikinn tíma með því að vanda okkur
- Þurfum að hugsa vel um samræmi
- Samræmi á heitum (ekki
username
,userName
oguser-name
) - Samræmi á URI (ekki
/get-users
,/products
og/cats
) - Samræmi á villuskilaboðum
- Samræmi á heitum (ekki
- Hugsum heildstætt, hvað gerist í hverju tilfelli?
- Hvað ef beðið er um eitthvað sem er ekki til
- Hvað ef villa kemur upp
- o.s.fr.
- Oft gott að aðskilja virknina okkar frá vefþjónustulaginu
- Vefþjónustan kemur sem „þunnt lag“ ofan á virkni
- Stundum þurfum við að grípa til þess að búa til fyrirspurnir með strengjameðhöndlun 🙈
- Ekki allt getur farið í prepared statement, t.d. ef við viljum dýnamískt breyta
ORDER BY
- Eða gera
PATCH
köll - VERÐUM AÐ FARA VARLEGA, svona kóði er einstaklega viðkvæmur fyrir SQL injection
async function list(req, res) {
const asc = req.query.order === 'ASC';
// röðum e. id í ascending (ASC) eða
// descending (DESC) röð eftir því
// hvort boolean gildi sé satt eða ekki
// Notum ekkert frá notanda í dýnamískrí
// fyrirsp. GETUM AÐEINS HAFT TVÖ GILDI!
const order = asc ? 'id ASC' : 'id DESC';
const q = `
SELECT * FROM items ORDER BY ${order}`;
const result = await query(q);
}
- Getum notað
RETURNING
syntax í postgres til að fá til baka færslu sem búin var til - Þurfum ekki aðra fyrirspurn til að fletta upp reitum eins og
id
eðacreated
INSERT INTO items (text) VALUES ('foo') RETURNING id, text, created;
- Getum reynt að eyða færslu sem er ekki til
- Þurfum samt að aðskilja á milli færslu sem var til og eytt
- og færslu sem var ekki til...
- Þegar átt var við raðir mun
rowCount
skila fjölda raða sem átt var við
async function deleteRow(id) {
const q = `
DELETE FROM todos WHERE id = $1`;
const result = await query(q, [id]);
// true ef færslu eytt, annars false
return result.rowCount === 1;
}
- Þegar við erum að vinna með mikið af gögnum þurfum við oft á tíðum að takmarka hversu miklu er skilað
- Ekki vænlegt að skila öllum miljón færslum til notanda
- Yfirleitt útfært með því að skila síðum
- Síður takmarkast af fjölda færslna per síðu (
limit
) og hve mörgum við sleppum (offset
)limit=10, offset=10
birtir færslur 11-20
- Einnig hægt að einfalda í
page
, höfum þá skilgreindan fjölda per síðu ogpage
er margföldun á honumpage=1
eru færslur 1-10,page=4
eru færslur 31-40
- Getum skilað upplýsingum um síðu í svari eða header
- RFC 5988 skilgreinir web linking og hvernig nota megi í hausum
Link: <http://api.example.com/?page=1>; rel="previous", <http://api.example.com/?page=3>; rel="next",
- Getur verið einfaldara að skila í svari, hægt að nota
_link
sem er skilgreint í HAL
{
"_links": {
"self": {
"href": "http://api.example.com/?page=2"
},
"previous": {
"href": "http://api.example.com/?page=1"
},
"next": {
"href": "http://api.example.com/?page=3"
}
},
"items": []
}
- Getum sent
offset
oglimit
í postgres query SELECT * FROM foo OFFSET 0 LIMIT 10
- Sendum sem parameterized gildi
- Með stórum gagnasettum fylgir of einnig þörf til að geta leitað í þeim
- Nokkrar leiðir mögulegar
- Fyrsta lausn sem manni gæti dottið í hug væri að nota
LIKE
leit í gagnagrunni - Veljum dálka til að leita í og setjum leitarskilyrði inn með
%
... WHERE description LIKE '%foo%'
- Afskaplega slæm lausn þar sem fyrir hvert skilyrði þarf að skoða alla dálka í töflu
- Getum þá útbúið index fyrir töflu, veljum einhverja dálka og geymum þá sérstaklega
- Til sérstök lausn á þessu með full-text index, allur texti settur í index og leitað sérstaklega í honum
- Getur þurft sérstaka uppsetningu í gagnagrunni
- Önnur algeng lausn er algjörlega aðskilinn leitarþjónusta
- Tengjum gögnin í okkar gagnagrunn við leitarþjónustu, t.d. með reglulegum keyrslum eða triggers
- Bíður upp á sérhæfða leit með setningarfræði og álíka
- T.d. elasticsearch eða algolia
- Postgres bíður upp á að leita án index
- Leitar m.t.t. málfræði ef hún er skilgreind í postgres
- Enska er sjálfgefið uppsett
- Skilgreinum í hvaða dálk við leitum og eftir hverju við leitum
to_tsvector
ogto_tsquery
to_tsquery
getur tekið við breytum og syntax, getum lent í að fá villurplainto_tsquery
leitar bara
SELECT title
FROM pgweb
WHERE
to_tsvector('english', body)
@@
to_tsquery('english', 'friend');
- Þegar við setjum upp töflur í gagnagrunn þurfum við að hugsa um hvaða verkefni við erum að leysa
- Hvernig gagnamódelið (e. data model) okkar lítur út
- Erum að útbúa einfaldaða birtingarmynd af raunveruleikanum
- Þurfum að útbúa tengingar á milli taflna
- One-to-one tengingar eiga við þegar eining á tengingu við nákvæmlega eina aðra einingu
- Sú eining á tengingu til baka í aðeins þá einingu
- T.d. höfuðborg ↔ land, íslensk kennitala ↔ íslenskur ríkisborgari
- One-to-many á við þegar eining á tengingu við nákvæmlega eina aðra einingu
- Sú eining getur átt tengingar við margar einingar
- T.d. bók á margar blaðsíður, en blaðsíðurnar eiga aðeins eina bók
- Einnig hægt að snúa við og módela sem many-to-one
- Í gagnagrunni notum við vísun úr einni töflu í aðra til að módela þessar tenginar
- Þó getur one-to-one verið „brotið“ á þennan máta þar sem við getum myndað one-to-many tengingu
- Oft eru gögnin hreinlega saman í einni töflu, t.d. nafn og kennitala
- Við tryggjum tenginguna með því að skilgreina foreign key á milli taflanna
- Getum ekki bætt við færslu ef vísun er ekki til
- Getum ekki hent færslu ef önnur vísar í hana
- Mikill kostur og tryggir öryggi gagnanna okkar
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE books (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
category INTEGER NOT NULL,
CONSTRAINT category FOREIGN KEY (category)
REFERENCES categories (id)
);
- Many-to-many á við þegar eining á tenginar í margar aðrar einingar
- Þær einingar eiga einnig tengingar í margar aðrar einingar
- T.d. nemandi getur verið skráður í margar áfanga, áfangar hafa marga nemendur
- Many-to-many tenginar þurfum við að skilgreina í sérstökum tengitöflum (e. join table)
- Hver færsla í tengitöflu tengir tvær raðir í öðrum töflum
- Getum hugsanlega bætt við auka lýsigögnum um sambandið
CREATE TABLE users_books (
id SERIAL PRIMARY KEY,
"user" INTEGER NOT NULL,
book INTEGER NOT NULL,
rating INTEGER, -- Lýsigögn um sambandið!
CONSTRAINT book FOREIGN KEY (book)
REFERENCES books (id),
CONSTRAINT "user" FOREIGN KEY ("user")
REFERENCES users (id)
);
- Þegar við viljum fá gögn úr þessum tengdu töflum þurfum við að nota join
- Sameinar sett úr fleiri en einni töflu (eða úr sömu töflu) miðað við ákveðin skilyrði
- „Gefðu mér id og nafn á bókum úr
books
og þann flokk sem hún tilheyrir ícategories
“
- „Gefðu mér id og nafn á bókum úr
SELECT
books.id AS id, books.name AS name,
categories.name AS category
FROM
books
JOIN
-- Taflan sem við viljum tengja
categories ON
-- Skilyrðin á tengingu
books.category = categories.id
- Verðum að vísa í dálka per töflu (
books.id
) þar sem töflurnar geta átt dálka með sama heiti AS
leyfir okkur að gefa nýtt nafn í niðurstöðumJOIN
geta orðið flóknari (left
,right
,cross
), förum ekki nánar í það hér
- Þegar við erum að vinna með tengd gögn getum við dottið í það „anti-pattern“ að framkvæma
n+1
fyrirspurnir 1
fyrirspurn fyrir lista afn
færslum,n
fyrirspurnir til að fá gögn um hverja færslu- Ættum alltaf að getað notað join í staðinn, eða módelað gögnin þannig að það þurfi ekki