ЁЯФМ рдзрдбрд╛ 03 тАФ API design: рдЖрдзреА рдХрд░рд╛рд░
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 03 ┬╖ рдорд╛рдЧреЗ: lesson-02-capacity ┬╖ рдкреБрдвреЗ: lesson-04-data-model
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ02, рдЖрдгрд┐ рдкрджреНрдзрддреАрдЪреА рддрд┐рд╕рд░реА рдкрд╛рдпрд░реА: app рдЖрдгрд┐ server рдпрд╛рдВрдЪреНрдпрд╛рддрд▓рд╛ рдХрд░рд╛рд░ (contract), code
рдЪреНрдпрд╛ рдЖрдзреА рд▓рд┐рд╣рд┐рд▓реЗрд▓рд╛. рдХреГрддреАрдВрдРрд╡рдЬреА resources, рдпреЛрдЧреНрдп status codes, рдкреБрдиреНрд╣рд╛ рдкрд╛рдард╡рд▓реЗрд▓реА post рджреЛрдирджрд╛
рд▓рд╛рдЧреВ рдирдпреЗ рдореНрд╣рдгреВрди idempotency keys, OFFSET рдРрд╡рдЬреА cursor pagination, рдЖрдгрд┐ рдкреНрд░рдХрд╛рд╢рд┐рдд field
рдХрдзреАрд╣реА рди рдореЛрдбрдгрд╛рд░реЗ versioning. design/demo.py рдордзреАрд▓ api() рд╕реВрдЪрдирд╛
рдлрд▓рдХрд╛рдЪрд╛ рдХрд░рд╛рд░ рдЫрд╛рдкрддреЗ; design/designs.py рдордзреАрд▓ pagination_cost()
рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХрд╛рд░рдЪреЗ pagination database рд▓рд╛ рдХрд┐рддреА rows рдЪрд╛рд▓рд╛рдпрд▓рд╛ рд▓рд╛рд╡рддреЗ рддреЗ рдореЛрдЬрддреЗ.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдирд┐рдпреЛрдЬрди рдХрд╛рд░реНрдпрд╛рд▓рдп рд╕реНрд╡рддрдГ рд╣реЙрд▓ рдмрд╛рдВрдзрдгрд╛рд░ рдирд╛рд╣реА. рдмрд╛рдВрдзрдХрд╛рдо рдХрд░рдгрд╛рд░реЗ рдмрд╛рдВрдзрддреАрд▓ тАФ рдЖрдгрд┐ рд╕реБрддрд╛рд░, рд╡реАрдЬрддрдВрддреНрд░реА рдЖрдгрд┐ рд░рдВрдЧрд╛рд░реА рд╕рдЧрд│реЗ рдПрдХрд╛рдЪ рд╡реЗрд│реА рдХрд╛рдо рдХрд░рддрд╛рдд. рдкреНрд░рддреНрдпреЗрдХрд╛рдиреЗ рджрд╛рд░реЗ рдХреБрдареЗ рдЖрд╣реЗрдд рдпрд╛рдЪрд╛ рдЕрдВрджрд╛рдЬ рдмрд╛рдВрдзрд▓рд╛, рддрд░ рдХрд╛рд╣реАрдЪ рдЬреБрд│рдгрд╛рд░ рдирд╛рд╣реА.
рдореНрд╣рдгреВрди рджреАрдкрд┐рдХрд╛ рдЖрдзреА рджрд╛рд░рд╛рдВрдЪрд╛ рдЖрд░рд╛рдЦрдбрд╛ ЁЯЪк рдХрд╛рдврддреЗ: рд╣реЗ рджрд╛рд░ рдореБрд▓рд╛рдВрдирд╛ рдЖрдд рдпреЗрдгреНрдпрд╛рд╕рд╛рдареА, рддреА рдЦрд┐рдбрдХреА рдлреЙрд░реНрдо рд╡рд╛рдЯрдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рджрд╛рд░рд╛рд╡рд░ рддрд┐рдереЗ рдХрд╛рдп рд╣реЛрддреЗ рддреЗ рд╕рд╛рдВрдЧрдгрд╛рд░реА рдкрд╛рдЯреА. рджрд╛рд░рд╛рдВрдЪреНрдпрд╛ рдЖрд░рд╛рдЦрдбреНрдпрд╛рд╡рд░ рд╕рд╣реА рдЭрд╛рд▓реА рдХреА рд╕рдЧрд│реЗ рдПрдХрд╛рдЪ рд╡реЗрд│реА рдХрд╛рдо рдХрд░реВ рд╢рдХрддрд╛рдд. App team рдЖрд░рд╛рдЦрдбреНрдпрд╛рдкреНрд░рдорд╛рдгреЗ phone app рдмрд╛рдВрдзрддреЗ; server team рдЖрд░рд╛рдЦрдбреНрдпрд╛рдкреНрд░рдорд╛рдгреЗ server рдмрд╛рдВрдзрддреЗ.
рджреАрдкрд┐рдХрд╛рдЪреНрдпрд╛ рдЖрд░рд╛рдЦрдбреНрдпрд╛рдд рдЖрдгрдЦреА рджреЛрди рдирд┐рдпрдо:
- рдкреНрд░рддреНрдпреЗрдХ рдлреЙрд░реНрдорд╡рд░ рдПрдХ рд╢рд┐рдХреНрдХрд╛ ЁЯФЦ. рдкрд╣рд┐рд▓реА рд╕реВрдЪрдирд╛ рдкреЛрд╣реЛрдЪрд▓реА рдХреА рдирд╛рд╣реА рдпрд╛рдЪреА рдЦрд╛рддреНрд░реА рдирд╕рд▓реНрдпрд╛рдиреЗ рдПрдЦрд╛рджреНрдпрд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдиреЗ рддреАрдЪ рд╕реВрдЪрдирд╛ рджреЛрдирджрд╛ рджрд┐рд▓реА, рддрд░ рдХрд╛рд░реНрдпрд╛рд▓рдп рддреЛрдЪ рд╢рд┐рдХреНрдХрд╛ рдкрд╛рд╣рддреЗ рдЖрдгрд┐ рддреА рджреЛрдирджрд╛ рд▓рд╛рд╡рдд рдирд╛рд╣реА.
- "рдЗрдереВрди рдкреБрдвреЗ рдЪрд╛рд▓реВ рдареЗрд╡рд╛" рдЪрд┐рдареНрдареНрдпрд╛ ЁЯФЦ. рдЬреБрдиреНрдпрд╛ рд╕реВрдЪрдирд╛ рд╡рд╛рдЪрдгрд╛рд▒реНрдпрд╛ рдкрд╛рд▓рдХрд╛рд▓рд╛ рдПрдХрд╛ рд╡реЗрд│реА 20 рд╕реВрдЪрдирд╛ рдЖрдгрд┐ "рд╕реВрдЪрдирд╛ 90 рдирдВрддрд░ рдкреБрдвреЗ рдЪрд╛рд▓реВ рдареЗрд╡рд╛" рдЕрд╢реА рдЪрд┐рдареНрдареА рдорд┐рд│рддреЗ. рдХрд╛рд░рдХреВрди рдереЗрдЯ рд╕реВрдЪрдирд╛ 90 рдХрдбреЗ рдЬрд╛рддреЗ тАФ рддреА рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА рдкрд╣рд┐рд▓реНрдпрд╛ рд╕реВрдЪрдиреЗрдкрд╛рд╕реВрди рдкреБрдиреНрд╣рд╛ рдореЛрдЬрдд рдирд╛рд╣реА.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
app["ЁЯУ▒ parent / teacher app"]
subgraph api["ЁЯФМ the contract ┬╖ /v1"]
p["POST /classes/3A/notices<br/>Idempotency-Key<br/>тЖТ 201 + Location"]
g["GET /classes/3A/notices<br/>?limit=20&cursor=тАж<br/>тЖТ 200 {items, next_cursor}"]
f["GET /parents/me/feed<br/>?limit=20"]
end
err["тЪая╕П 400 ┬╖ 401 ┬╖ 403 ┬╖ 404 ┬╖ 409 ┬╖ 429"]
pg["ЁЯУД page 5,000<br/>OFFSET walks 100,000 rows<br/>cursor walks 20"]
app --> p
app --> g
app --> f
api --> err
g --> pg
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/system-design/lesson-diagrams.html#l03
тЭУ рдХрд╛рдп
- API рдХрд░рд╛рд░ (contract) тАФ requests, рддреНрдпрд╛рдВрдЪреЗ inputs, рддреНрдпрд╛рдВрдЪреА рдЙрддреНрддрд░реЗ рдЖрдгрд┐ рддреНрдпрд╛рдВрдЪреЗ errors рдпрд╛рдВрдЪреА рд▓рд┐рд╣реВрди рдареЗрд╡рд▓реЗрд▓реА рдпрд╛рджреА (HTTP рд╕рд╛рдареА, рд╕рд╣рд╕рд╛ рдПрдХрд╛ OpenAPI file рдордзреНрдпреЗ). Teams рдПрдХрд╛рдЪ рд╡реЗрд│реА рддреНрдпрд╛рдЪреНрдпрд╛ рдЖрдзрд╛рд░реЗ рдмрд╛рдВрдзрддрд╛рдд.
- рдХреГрддреА рдирд╡реНрд╣реЗ, resources (REST) тАФ URLs рдЧреЛрд╖реНрдЯреАрдВрдЪреА рдирд╛рд╡реЗ рд╕рд╛рдВрдЧрддрд╛рдд (
/classes/3A/notices), рдЖрдгрд┐ HTTP method рдХрд╛рдп рдХрд░рд╛рдпрдЪреЗ рддреЗ рд╕рд╛рдВрдЧрддреЗ:GETрд╡рд╛рдЪрддреЛ,POSTрдмрдирд╡рддреЛ,PUT/PATCHрдмрджрд▓рддреЛ,DELETEрдХрд╛рдвреВрди рдЯрд╛рдХрддреЛ./postNoticeрдХрд┐рдВрд╡рд╛/getNoticesForClassрдирд╛рд╣реА. - Status codes тАФ client рд╕рдЧрд│реНрдпрд╛рдд рдЖрдзреА рд╡рд╛рдЪрддреЛ рддреА рдЧреЛрд╖реНрдЯ:
200OK ┬╖201Created (+ рдирд╡реНрдпрд╛ рд╕реВрдЪрдиреЗрдЪрд╛ URL рдЕрд╕рд▓реЗрд▓рд╛Locationheader)400рдЪреБрдХреАрдЪреЗ input ┬╖401рддреБрдореНрд╣реА рдХреЛрдг? (login рдХреЗрд▓реЗрд▓реЗ рдирд╛рд╣реА) ┬╖403рддреБрдореНрд╣реА рдУрд│рдЦреАрдЪреЗ рдЖрд╣рд╛рдд, рдкрдг рд╣рд╛ рддреБрдордЪрд╛ рд╡рд░реНрдЧ рдирд╛рд╣реА ┬╖404рдЕрд╢реА рдЧреЛрд╖реНрдЯ рдирд╛рд╣реА ┬╖409рд╕рдВрдШрд░реНрд╖ (conflict) ┬╖429рд╣рд│реВ (рдзрдбрд╛ 12)5xxтАФ server рдЕрдкрдпрд╢реА рдЭрд╛рд▓рд╛; client рдирдВрддрд░ retry рдХрд░реВ рд╢рдХрддреЛ.
- Idempotency key тАФ client
POSTрд╡рд░ рдПрдХрд╛ header (Idempotency-Key) рдордзреНрдпреЗ рдареЗрд╡рддреЛ рддреЛ рдПрдХ рдЕрджреНрд╡рд┐рддреАрдп id. Server key рдЖрдгрд┐ рдЙрддреНрддрд░ рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рддреЛ. рддреАрдЪ key рдкреБрдиреНрд╣рд╛ рдЖрд▓реА (timeout рдирдВрддрд░рдЪрд╛ retry), рддрд░ server рдкрд╣рд┐рд▓реЗрдЪ рдЙрддреНрддрд░ рдкрд░рдд рджреЗрддреЛ рдЖрдгрд┐ рджреБрд╕рд░реА рд╕реВрдЪрдирд╛ рдмрдирд╡рдд рдирд╛рд╣реА.GET,PUTрдЖрдгрд┐DELETEрд░рдЪрдиреЗрдиреЗрдЪ idempotent рдЖрд╣реЗрдд;POSTрдирд╛рд╣реА тАФ рдореНрд╣рдгреВрдирдЪ рддреНрдпрд╛рд▓рд╛ key рд▓рд╛рдЧрддреЗ. (рддреАрдЪ key рд╡реЗрдЧрд│реНрдпрд╛ body рд╕рд╣ рд╡рд╛рдкрд░рд▓реА, рддрд░ рддреЛ client рдЪрд╛ bug:409рдХрд┐рдВрд╡рд╛422рдЙрддреНрддрд░ рджреНрдпрд╛.) - Pagination тАФ "рд╕рдЧрд│реНрдпрд╛ рд╕реВрдЪрдирд╛" рдХрдзреАрдЪ рдкрд░рдд рджреЗрдК рдирдХрд╛. рдПрдХ page рдкрд░рдд рджреНрдпрд╛:
- OFFSET (
?page=5000тЖТOFFSET 99980 LIMIT 20) тАФ рд╕реЛрдкреЗ, рдкрдг database рдЖрдзреАрдЪреА рдкреНрд░рддреНрдпреЗрдХ row рдЪрд╛рд▓рддреЛ рдЖрдгрд┐ рдлреЗрдХреВрди рджреЗрддреЛ. Page 5,000 100,000 rows рдЪрд╛рд▓рддреЛ. рддреБрдореНрд╣реА рд╡рд╛рдЪрдд рдЕрд╕рддрд╛рдирд╛ рдЬреЛрдбрд▓реЗрд▓реНрдпрд╛ rows pages рд╕рд░рдХрд╡рддрд╛рдд, рдореНрд╣рдгреВрди рдПрдЦрд╛рджреА рд╕реВрдЪрдирд╛ рджреЛрдирджрд╛ рджрд┐рд╕рддреЗ рдХрд┐рдВрд╡рд╛ рдЪреБрдХрддреЗ. - Cursor (keyset) тАФ рдЙрддреНрддрд░рд╛рдд
next_cursorрдЕрд╕рддреЛ, "рдореА рдкрд╛рдард╡рд▓реЗрд▓реНрдпрд╛ рд╢реЗрд╡рдЯрдЪреНрдпрд╛ item рдирдВрддрд░" рдпрд╛рдЪреЗ рдПрдХ рдЕрдкрд╛рд░рджрд░реНрд╢рдХ (opaque) token (eyJpZCI6OTB9рд╣реЗ{"id":90}рдЪреЗ base64 рдЖрд╣реЗ). Database index рд╡рд╛рдкрд░реВрди рдереЗрдЯ рддрд┐рдереЗ seek рдХрд░рддреЛ. рдкреНрд░рддреНрдпреЗрдХ page рдлрдХреНрдд 20 rows рдЪрд╛рд▓рддреЛ.
- OFFSET (
- Versioning тАФ version path рдордзреНрдпреЗ (
/v1/тАж) рдХрд┐рдВрд╡рд╛ header рдордзреНрдпреЗ рдареЗрд╡рд╛. рдкреНрд░рдХрд╛рд╢рд┐рдд field рдХрдзреАрд╣реА рдореЛрдбреВ рдирдХрд╛: рдирд╡реЗ fields рдЬреЛрдбрд╛, рдЬреБрдиреЗ rename рдХрд┐рдВрд╡рд╛ рдХрд╛рдвреВ рдирдХрд╛. рдЬреБрдиреЗ apps рд╡рд░реНрд╖рд╛рдиреБрд╡рд░реНрд╖реЗ phones рд╡рд░ рд░рд╛рд╣рддрд╛рдд. - REST vs gRPC vs GraphQL (рдереЛрдбрдХреНрдпрд╛рдд):
- REST + JSON тАФ public рдЖрдгрд┐ mobile APIs рд╕рд╛рдареА default; cache рд▓рд╛ рд╕реЛрдпреАрдЪреЗ, debug рдХрд░рд╛рдпрд▓рд╛ рд╕реЛрдкреЗ.
- gRPC тАФ binary, typed, рдЬрд▓рдж, streaming; рддреБрдордЪреНрдпрд╛рдЪ services рдордзрд▓реНрдпрд╛ рд╕рдВрд╡рд╛рджрд╛рд╕рд╛рдареА рдЪрд╛рдВрдЧрд▓реЗ.
- GraphQL тАФ client рд▓рд╛ рд╣рд╡реЗ рддреЗрд╡рдвреЗрдЪ fields рдорд╛рдЧрддреЛ; рд╡реЗрдЧрд╡реЗрдЧрд│реНрдпрд╛ рдЧрд░рдЬрд╛рдВрдЪреНрдпрд╛ рдЕрдиреЗрдХ screens рд╕рд╛рдареА рдЪрд╛рдВрдЧрд▓реЗ, рдкрдг caching рдЖрдгрд┐ rate limits рдЕрд╡рдШрдб рд╣реЛрддрд╛рдд.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг API рд╣рд╛ рдирдВрддрд░ рдмрджрд▓рд╛рдпрд▓рд╛ рд╕рд░реНрд╡рд╛рдд рдЕрд╡рдШрдб рднрд╛рдЧ рдЖрд╣реЗ. рддреНрдпрд╛рдорд╛рдЧрдЪрд╛ database рддреБрдореНрд╣реА рдПрдХрд╛ weekend рдордзреНрдпреЗ
рдмрджрд▓реВ рд╢рдХрддрд╛. 5 million phones рдирд╛ app update рдХрд░рд╛рдпрд▓рд╛ рд▓рд╛рд╡реВ рд╢рдХрдд рдирд╛рд╣реА. рдЖрдгрд┐ рдХрд░рд╛рд░ рд░рдЪрдиреЗрдЪреА рд╡рдЪрдиреЗ рд╡рд╛рд╣рддреЛ:
network рдмрд┐рдШрдбрд▓реЗ рддрд░реА idempotency key "рд╕реВрдЪрдирд╛ рдХрдзреАрд╣реА рджреЛрдирджрд╛ post рд╣реЛрдд рдирд╛рд╣реА" рд╣реЗ рдЦрд░реЗ рдареЗрд╡рддреЗ; cursor
pagination page 5,000 рд╡рд░рд╣реА "read p99 < 200 ms" рдЦрд░реЗ рдареЗрд╡рддреЗ; 403 "рдкрд╛рд▓рдХрд╛рд▓рд╛ рдлрдХреНрдд рддрд┐рдЪреНрдпрд╛ рдореБрд▓рд╛рдВрдЪреЗ
рд╡рд░реНрдЧ рджрд┐рд╕рддрд╛рдд" рд╣реЗ рдХрд░рд╛рд░рд╛рддрдЪ рджрд┐рд╕рдгрд╛рд░реЗ рдареЗрд╡рддреЗ (рдзрдбрд╛ 14).
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
design/demo.py рдордзреАрд▓ api() рддреАрди calls рдЖрдгрд┐ errors рдЫрд╛рдкрддреЗ,
рдордЧ design/designs.py рдордзреАрд▓ pagination_cost(page, page_size, mode) рд▓рд╛
рд╡рд┐рдЪрд╛рд░рддреЗ рдХреА рдкреНрд░рддреНрдпреЗрдХ mode рдХрд┐рддреА rows рдЪрд╛рд▓рддреЛ: OFFSET рд╕рд╛рдареА page ├Ч page_size, cursor рд╕рд╛рдареА
page_size. Snippet рдордзреНрдпреЗ рддреБрдореНрд╣реА рдПрдХ рдЫреЛрдЯреЗ idempotency store рд╕реБрджреНрдзрд╛ рдЪрд╛рд▓рд╡рддрд╛ тАФ key рдкрд╛рд╕реВрди
рдЙрддреНрддрд░рд╛рдкрд░реНрдпрдВрддрдЪреА рдПрдХ dictionary тАФ рдЖрдгрд┐ timeout рдирдВрддрд░ phone рдХрд░рддреЛ рддрд╢реА рддреАрдЪ key рджреЛрдирджрд╛ рдкрд╛рдард╡рддрд╛.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 design/demo.py api
python3 - <<'EOF'
import sys; sys.path.insert(0, "design"); from designs import pagination_cost
for size in (20, 100):
for page in (1, 500, 50_000):
print(f"{size:>3} per page, page {page:>6,}: OFFSET {pagination_cost(page, size, 'offset'):>9,} rows ┬╖ cursor {pagination_cost(page, size, 'cursor'):>3}")
seen = {}
def post(key, body):
if key in seen: return 200, seen[key], "replayed"
seen[key] = f"notice-{len(seen) + 1}"; return 201, seen[key], "created"
print(post("k-7f3a", {"title": "Trip"}))
print(post("k-7f3a", {"title": "Trip"})) # the phone retried after a timeout
print(post("k-9b21", {"title": "Exam"}))
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
api рдЫрд╛рдкрддреЗ:
тФАтФА the contract first тАФ resources, not actions:
POST /classes/3A/notices body {title, text, urgent} ┬╖ header Idempotency-Key тЖТ 201 + Location
GET /classes/3A/notices?limit=20&cursor=eyJpZCI6OTB9 тЖТ 200 {items, next_cursor}
GET /parents/me/feed?limit=20 тЖТ the notices of all my children's classes
errors: 400 bad input ┬╖ 401 who are you ┬╖ 403 not your class ┬╖ 404 ┬╖ 409 key reused with a different body ┬╖ 429 slow down
тФАтФА pagination: rows the database walks to show page N (20 per page)
page 1: OFFSET walks 20 rows ┬╖ a cursor walks 20
page 50: OFFSET walks 1,000 rows ┬╖ a cursor walks 20
page 5000: OFFSET walks 100,000 rows ┬╖ a cursor walks 20
version in the path (/v1) or a header; never break a published field тАФ add new ones
рддреБрдордЪрд╛ snippet рдЫрд╛рдкрддреЛ:
20 per page, page 1: OFFSET 20 rows ┬╖ cursor 20
20 per page, page 500: OFFSET 10,000 rows ┬╖ cursor 20
20 per page, page 50,000: OFFSET 1,000,000 rows ┬╖ cursor 20
100 per page, page 1: OFFSET 100 rows ┬╖ cursor 100
100 per page, page 500: OFFSET 50,000 rows ┬╖ cursor 100
100 per page, page 50,000: OFFSET 5,000,000 rows ┬╖ cursor 100
(201, 'notice-1', 'created')
(200, 'notice-1', 'replayed')
(201, 'notice-2', 'created')
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
OFFSET рдЪрд╛ рдЦрд░реНрдЪ page рдХреНрд░рдорд╛рдВрдХрд╛рд╕реЛрдмрдд рд╡рд╛рдврддреЛ тАФ page 50,000 20 рд╕реВрдЪрдирд╛ рджрд╛рдЦрд╡рдгреНрдпрд╛рд╕рд╛рдареА рджрд╣рд╛ рд▓рд╛рдЦ rows
рдЪрд╛рд▓рддреЛ. Cursor рдЪрд╛ рдЦрд░реНрдЪ page 1 рдЖрдгрд┐ page 50,000 рд╡рд░ рд╕рд╛рд░рдЦрд╛рдЪ. рдЖрдгрд┐ рддреАрдЪ Idempotency-Key рджреЛрдирджрд╛
рдкрд╛рдард╡рд▓реА рдХреА рддреАрдЪ рд╕реВрдЪрдирд╛ рдкрд░рдд рдорд┐рд│рддреЗ (notice-1, рджреБрд╕рд▒реНрдпрд╛ рд╡реЗрд│реА 200 рдЙрддреНрддрд░) тАФ рдПрдХ рд╕реВрдЪрдирд╛, рджреЛрди рдирд╛рд╣реА.
рдирд╡реА key рдирд╡реА рд╕реВрдЪрдирд╛ рдмрдирд╡рддреЗ.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- URLs рдордзреНрдпреЗ рдХреНрд░рд┐рдпрд╛рдкрджреЗ:
/createNotice,/getAllNotices - рд╕рдЧрд│реНрдпрд╛рд╕рд╛рдареА
200, рдЖрдд{"error": тАж}тАФ clients рдЖрдгрд┐ load balancers рдирд╛ рдлрд░рдХ рдХрд│рдд рдирд╛рд╣реА 401рдЖрдгрд┐403рдЪреА рдЧрд▓реНрд▓рдд: 401 = "рддреБрдореНрд╣реА рдХреЛрдг?", 403 = "рдореА рддреБрдореНрд╣рд╛рд▓рд╛ рдУрд│рдЦрддреЛ; рдкрд░рд╡рд╛рдирдЧреА рдирд╛рд╣реА"- mobile network рд╡рд░ idempotency key рд╢рд┐рд╡рд╛рдп
POSTтАФ рдкреНрд░рддреНрдпреЗрдХ timeout duplicate рдЪрд╛ рдзреЛрдХрд╛ рдЖрдгрддреЛ - limit рд╢рд┐рд╡рд╛рдп рдкреНрд░рддреНрдпреЗрдХ row рдкрд░рдд рджреЗрдгреЗ; рдХрд╛рдпрдо рд╡рд╛рдврдд рдЬрд╛рдгрд╛рд▒реНрдпрд╛ table рд╡рд░ OFFSET
- рдкреНрд░рдХрд╛рд╢рд┐рдд field "рдирд╡реЗ рдирд╛рд╡ рдЬрд╛рд╕реНрдд рдЫрд╛рди рдЖрд╣реЗ рдореНрд╣рдгреВрди" rename рдХрд░рдгреЗ тАФ рдЬреБрдиреЗ apps рдореЛрдбрддрд╛рдд
- client рд╡рд╛рдЪреВ рдЖрдгрд┐ рдмрджрд▓реВ рд╢рдХреЗрд▓ рдЕрд╕рд╛ cursor (
?after_id=90) рдЖрдгрд┐ рдордЧ access рд╕рд╛рдареА рддреНрдпрд╛рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ тАФ рддреЛ opaque рдареЗрд╡рд╛, permissions рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА рддрдкрд╛рд╕рд╛
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдХрд░рд╛рд░ repo рдордзреНрдпреЗ рдПрдХрд╛ OpenAPI file рдореНрд╣рдгреВрди рд░рд╛рд╣рддреЛ; server stubs, client code, docs рдЖрдгрд┐ tests рддреНрдпрд╛рдкрд╛рд╕реВрди рдмрдирддрд╛рдд. рд╕реВрдЪрдирд╛ рдлрд▓рдХрд╛рдЪреНрдпрд╛ file рдЪрд╛ рдПрдХ рднрд╛рдЧ:
openapi: 3.1.0
info: { title: Notice board, version: "1.0" }
paths:
/v1/classes/{classId}/notices:
post:
parameters:
- { name: classId, in: path, required: true, schema: { type: string } }
- { name: Idempotency-Key, in: header, required: true, schema: { type: string, maxLength: 64 } }
requestBody:
content:
application/json:
schema: { $ref: "#/components/schemas/NewNotice" }
responses:
"201": { description: Created, headers: { Location: { schema: { type: string } } } }
"403": { description: Not your class }
"409": { description: Key reused with a different body }
get:
parameters:
- { name: limit, in: query, schema: { type: integer, maximum: 100, default: 20 } }
- { name: cursor, in: query, schema: { type: string } }
responses:
"200": { description: A page of notices, newest first }
Server рдЪрд╛рд▓рд╡рддреЛ рддреА cursor query тАФ index рд╡рд░рдЪрд╛ keyset seek, рдкреНрд░рддреНрдпреЗрдХ page рд╡рд░ рддреЛрдЪ рдЦрд░реНрдЪ:
SELECT id, title, created_at FROM notices
WHERE class_id = '3A' AND (created_at, id) < ($1, $2) -- values decoded from the cursor
ORDER BY created_at DESC, id DESC LIMIT 20;
On a real account тАФ service рдЪреНрдпрд╛ рдкреБрдвреЗ рдЕрд╕рд▓реЗрд▓рд╛ API рддреБрдордЪрд╛ code рдЪрд╛рд▓рдгреНрдпрд╛рдЖрдзреАрдЪ рдХрд░рд╛рд░ рддрдкрд╛рд╕реВ рд╢рдХрддреЛ, рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде рддреАрдЪ OpenAPI file Amazon API Gateway рдордзреНрдпреЗ import рдХрд░реВрди:
aws apigateway import-rest-api --body fileb://openapi.yaml
рдЕрдзрд┐рдХ рдЦреЛрд▓рд╛рдд: API рд╢рд╛рд│рд╛ (REST, status codes, auth, pagination, gRPC рдЖрдгрд┐ GraphQL) рдЖрдгрд┐ API Gateway рд╢рд╛рд│рд╛.
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: OpenAPI file code рд╕рд╛рд░рдЦреАрдЪ review рдХрд░рд╛, рдЖрдгрд┐ рдПрдЦрд╛рджрд╛ рдмрджрд▓ рдкреНрд░рдХрд╛рд╢рд┐рдд field рдХрд╛рдврддреЛ рдХрд┐рдВрд╡рд╛ rename рдХрд░рддреЛ рддреЗрд╡реНрд╣рд╛ рдЕрдкрдпрд╢реА рд╣реЛрдгрд╛рд░реА рддрдкрд╛рд╕рдгреА CI рдордзреНрдпреЗ рдЪрд╛рд▓рд╡рд╛. рдореЛрдбрдгрд╛рд░реЗ рдмрджрд▓ рд╣рд╛ рдирд┐рд░реНрдгрдп рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡рд╛, рдЕрдкрдШрд╛рдд рдХрдзреАрдЪ рдирд╛рд╣реА.
тПня╕П рдкреБрдвреЗ
рдХрд░рд╛рд░ рд╕рд╛рдВрдЧрддреЛ app рдХрд╛рдп рд╡рд┐рдЪрд╛рд░рддреЛ. рдЖрддрд╛: рдЙрддреНрддрд░ рдХреБрдареЗ рд░рд╛рд╣рддреЗ? рдирд┐рдпреЛрдЬрди рдХрд╛рд░реНрдпрд╛рд▓рдп data рдЪреЗ model рддреНрдпрд╛рд▓рд╛ рд╡рд┐рдЪрд╛рд░рд▓реНрдпрд╛ рдЬрд╛рдгрд╛рд▒реНрдпрд╛ рдкреНрд░рд╢реНрдирд╛рдВрд╡рд░реВрди рдмрдирд╡рддреЗ.
git checkout lesson-04-data-model