Галоўная / Артыкулы / З’явленне бэкстэпу ў мовах прымітак API без звукоў за дапамойкай типаў, выведзеных з прыкладаў, і Zod

З’явленне бэкстэпу ў мовах прымітак API без звукоў за дапамойкай типаў, выведзеных з прыкладаў, і Zod

Чаму рукапісныя типы TypeScript для сторонніх API стаюць застарэлымі, як адгукаванне типоў і схем Zod з рэальных адпаведзяў дапамагае, і як разлікі снэпшотаў адкрываюць адхыленні.

1565 слоў

API-я трэціх сторон зменяюцца без паведамлення, і TypeScript гэтага не зазначыць, таму што вашы типы описваюць, як выглядала адпаведна вясноўка, калі яе стварылі, а не як яна выглядае сёння. У гэтым артыкуле паспрабована раз'ясніць, як выходзіць такая проблема, чаму стварэнне типоў на аднойчынных вясноўках краща, чым ўвесь час іх пісаць вручную, і чаму справжнім захопам являецца паўтарны аналіз новых вясноўкаў пры супоракам з захаваным фіксам. Вы таксама пазнаеце, як гэты падход адносіцца да OpenAPI і тэставання контрактаў, а дзе ён не падходзіць.

Як поле з новым іменем працягваецца без прытаманных наследків

Разглядзім кранцевую частыну, яка інтегруецца з прадаўчым апаратам. Аднаго дня полье ў адной з адпаведзей прадаўчага апарату зменілася з user_id на userId. Не было жадных запісаў у чатголу змян, жадных аанунсаў і жадных падышэння версіі. Найбольш ймовірна, что інжынер з боку прадаўчага апарату выправіў неоднаковую назву, ўсе тэсты працавалі правільна, і змяна была запраўдзена.

Нічы не зламваецца на боку, які выкарыстоўвае даны, а гэта самэ ўсё і являе сабою проблему. Код усё ўсё чытае response.user_id, а TypeScript яго прымае, таму што інтэрфейс быў рукамі заданы месяцямі раней на адлік у Postman, які больш не відпавае рэальнасці. Ў тым інтэрфейсе яшчэ прыпускае наявнасць поля user_id. У часы выконання значэнне проста ёстся undefined. Прыблізна две недзелі тры варыянты коду таямна запісвалі undefined у поле з сумамі, пакуль апошней не быў выявлены баг у заявцы на падтрымку. Ніякіх апавешчэнняў не было, ніякая компіляцыя не стала „чорная“, і прыклад продолжаў робіць неправильнае без жаданняя прычыны.

Команды, якія даволі дзейнаюць з зовнішнімі API, практычна завжды сталквуюцца з чымось падобным.

Слабая точка — гэта месца, з якога беруцься типы

Тут не вина TypeScript. Проблема крыўціце ў магчымасцях, з якіх яны беруцца. Інтэрфейсы зазвычай спрыягаюцца як частка чагось автантычнага, такога як схема, кантракт або едынаковы источнік правды. На практыцы багато з іх выходзіць з аднаго прыкладу адпаведзі, які хтось перапісаў вручную. Эты інтэрфейс пасля чаго копіюецца ў калькі іншых файлаў і спрыягаецца як факт, і ніхто больш не адглядае яго, пакуль чыго-небудзь не зламаецца.

Рэальны кантракт — гэта тое, што API вяртае ў рэальных умовах працы. Ён знаходзіцца на сервере, які вы не кантролюеце, і можа быць зменены без вашага санкцыявання. Рукапісныя магчымасці — это кадруванне момента, які вже прыйшоў, і кампайлер не мае можлівасці пра гэта знайсці.

Еща існуе глыбэйшыя проблемы. Тыпы TypeScript зникаюць пад час кампілявання, таму ў часа выконання нічога не перакантролюецца. Якщо формат даных змяніцца, толькі перакантролюванне ў часе выконання, напрыклад, парсаванне адпаведзі да схемы Zod, ператварае непазначаны значэнне undefined на негайную, видную памылку.

Адгадваць тыпы на аднойчыні з калькамі рэальных адпаведзенняў

Найбольш падказвае тут не болей развіты TypeScript чы хитрыя гэнерыкі, а механічны процес: взяць адпаведзення, якія фактычна вернуў API, стварыць з іх тыпы і быць паведамленым як толькі рэальнасць перестане падходзіць.

У вхідных данных павярэбны быць справжнімі JSON-адказамі, а не дакументацыяй і не схемай. На ўсходзе з іхіх інструмент можа вывучыць як тип TypeScript, так і адпаведную схему Zod. Вывісканне кальколькох прыкладаў мае большое значэнне, чым можа здавацца. Адзін адказ паказвае, як можа выглядаць пэйлоад. Тры-чатыры адказы раскрываюць, якія польні ў працоўным режыме яўляюцца необав’язковымі, якія іноды маюць значэнне null, а таксама якія элементы масіву маюць неоднаковую структуру. Адзіны прыклад завжды вводзіць у глухар, калі ўсё працяўна не паказвае.

У прыкладзе нижэй прадаюцца два адказы для таго ж рэсурсу — адзін з user_id, другі з userId — і паказваецца тип TypeScript і схема Zod, вывучаныя з обох:

// paste these two responses in...
[
  {
    "user_id": "pot_00009exampleP0tOxWb",
    "name": "Wedding Fund",
    "balance": 550100,
    "currency": "GBP",
    "created": "2025-11-09T12:30:53.695Z",
    "updated": "2025-02-26T07:12:04.925Z"
  },
  {
    "userId": "pot_00009exampleP0tOxWb",
    "name": "Wedding Fund",
    "balance": 550,
    "currency": "EUR",
    "created": "2025-11-09T12:30:53.695Z",
    "updated": "2025-03-26T07:12:04.925Z"
  }
]

// ...get this out typescript
type Root = {
  user_id?: string
  name: string
  balance: number
  currency: string
  created: string
  updated: string
  userId?: string
}[]

// or ... get this out zod
import { z } from 'zod'

const Root = z.array(z.object({
  user_id: z.string().optional(),
  name: z.string(),
  balance: z.number(),
  currency: z.string(),
  created: z.string(),
  updated: z.string(),
  userId: z.string().optional(),
}))

Аккуратна пераглядзіце, што паказвае аджынуты рэзультат. Паколькі кожны імя з’являецца толькі ў аднам прыкладзе, і user_id, і userId стаюць необавязковымі. Гэта тэхнічна правдазе, але гэта таксама маскіруе змену іменавання: код, який чытае будзь-кой з эых полей, заўсёды выкарыстоўвае перакананне па типу, і адпаведны адказ, які не містыць нічога з гэтага, таксама праходзіць перакананне шэмы Zod. Прыклады таксама натыкаюць на проблэму, якую типавая інференція ніколі не зможа выявіць: balance падыходзіць з 550100 да 550, паколькі змянюецца currency, што можа значыць пераключэнне между дробнымі і цэлымі елементамі валюты. У обох случаях выведзены тип — number. Інференція паведамляе вам пра форму; яна не можа паведаміць вас пра значэнне.

Багатыя генераторы коду зупіняюцца на гэтым этапе. Пераход з нетыпаванага коду да тыпаванага є корыстным, але гэта не рашае проблэму змены структуры коду.

Снімкі стану і разлічыны выяўляюць змены

Найболей цінны ўздзеўк выходзіць пасля генеравання. Калі типы атрымліваюцца з рэальнай адпаведзі, тая адпаведзь можа быць збережаная як фотакадру. Кожны раз, калі вы адбierаеце новы прыклад з таго ж канцачнага пункту, вы порывнаеце яго з фотакадрам і атрымліваеце тачны аповешчэння пра тое, што змянілася: чы то поле пад новым іменем, значэнне, якое тепер можа быць null, там дзе раней быў просты string, чы то дадатковы ключ у внутранім об’екте. У змену на нечыткае „што-то зламалася гдзісь“, вы бачыце точную разніцу ў структуре.

Самэ гэтае параджэнне і ўсклікае разлік межа генераторам типоў і дэтектарам адхылень. Генераванне коду дапамагае перайсці ад нічога да коду з падазначэнням типоў. Дэтэкцыя адхылень запобегае таму, каб перайменаванне user_id на userId застаўся непазначаным у працоўнай сістэме на тыяжы недзелі. У прыводзе вышэй снімак разліку паказаў бы "user_id адключаны, userId даданы" замест таго, каб оба поля таямна сталі необавязковымі.

Зберагаюце пакеты дадзеных з працоўнай сістэмы на сваёй машыне

Для выявлення рэальнага схылу патрабуюцца рэальныя данні; синтэтычныя пэйлоады не будуць адкрываць тыя змены, якія вас цікавяць. Чырвоныя гэта прыватнасць стае абавязкай практычнага дизайна. Рэзультаты роботы ў працоўнай сіткі можу містіць інформацыю пра клёячых, таму ўводзьба іх у веб-форму, якая заваносіць іх на сервер трэцьей стороны, стварае новы рызык у обробцы даных. Адпаведныя інструменты павінны працаваць локальна, напрыклад, выключна ў вікні браузера або як скрыпт у вашай сэрвяравой базе, таким чынам пэйлоады ніколі не пакідаюць вашу среду.

Дзе гэты падход паслужыць, а дзе — ні

Выведчыцкія процесы на адной засадзе прымэроў з выявленням схылу не заменяюць OpenAPI чы іншых методаў тэставання кантрактаў, такіх як Pact. Якщо вы ёсце як прадаўца, так і спожывач і можете застосаваць шэму на самай вучоўцы, зрабіце гэта; гэта кращая далекасходная адпаведнасць.

Гэта тэхніка прызначаная для болейш распашчай і менш прываблівай ситуацыі: вы викорыстоўваеце API, якое не належыць вам, дакладная інструкцыя да яго ўстарела або відсутня, а створэнне кліента на адной з спецыфікацый OpenAPI немагчыма, таму што такой спецыфікацыі няма або ніхто ёй не давае доверы. Гэта описвае большасць інтеграцый з сервісамі обробкі платэжаў, внутранімі сервісамі, якія належаць іншым командам, а таксама API сторонніх постачальнаў. У такім разе фактычны адпаведзь ёсць ежым правдападобным даннем, якое ў вас є, таму самэ з яго і павінны быць выведзеныя вашы типы.

Зберагаеце сферу дзейнасці чыстай. JSON — уключана, TypeScript і Zod — выключаныя, плюс адказваўленне на змены формату — гэтага дастаткова. Прабавы карбавання XML, protobuf і ўсіх спецыяльных кейсаў схем прыводзяць да таго, што адточны інструмент стае неадкоўным. Чырпакавае пагляд на рашэнняя, якія часта пазорнаюць фронтэндам, можна знайсці ў паўтаральных памылках у контрактах API, якія пазорнаюць надзеям на надзеям фронтэнда.

Практычны першы тэст

Найлепшая адзінка для пробавы — інтэграцыя, якая вось-вось страдзе ад тыхоўскай змены формату. Возьміце стары адказ і няўзабавны адказ з таго ж эндпункта, працаваце іх праз аналіз і порэванне знімакаў, і разглядзіце, што будзе пазначана. Адразу бачыць рэальную історычную змену ў разліку пераканальней, чым будь-якія аргументы на падтрымку такога падходу.

Ключовыя выводы

  • Рукапісныя інтерфейсы для API трэціх сторон — это кадры з минулога, і TypeScript не можа визначыць, калі яны становяцца застарэлымі.
  • Выводзіце типы і схемы Zod на адказах з рэальных дадзеных, таму што колькасць прыкладаў паказвае необавязковыя, можлівыя да заняць і нэўнастойкія поля, якія аднолькі прыклад маскуюць.
  • Паўтараеце перакананне адказоў пад час выконання на межы API, так юбы змены формату выклікалі явныя парадкі, а не стваралі значэнне undefined.
  • Зберагаеце адказы як кадры і прагледваеце новыя прыклады па ўпорах з імі; сама толькі агрэгаваная інференцыя можа маскаваць перэіменаванне як два необавязковыя поля.
  • Зберагаеце пакеты дадзеных для працы ў локальным формате, і выбірайце тэсты на адпаведнасць OpenAPI чыў контракту, калі вы контролюеце обе стороны API.

Спадневанае чытанне

  • Валідны JSON, зламаны контракт: шаровыя перакрыцці для выяўлення регрэсій у пэйлоадзе — Навучыцеся выяўляць регрэсіі ў пэйлоадзе у формате JSON, якія парсуецца правільна: семантычныя разлікі, спецыфікацыя JSON Schema, асэрцыі бізнес-правіл у Node і меры, якія дазволяюць викорыстоўваць генераваныя типы.
  • Метод HTTP QUERY для команд фронтэнду: безпечныя чытання з тэлам — Дазведацеся, калі метод HTTP QUERY прыграе GET і POST для складных фільтраў, як выклікаць його за дапамой fetch і якія патрэбны падтрымка CORS, кешавання і інфраструктуры.