Престанете да се борите со еволуцијата на API: Архитектирајте за предвидлива еволуција
API-отстапувањето ретко доаѓа со драматично соопштение. Почнува како преименувано поле, опционален филтер, правило за пагинација што тивко се менува или зависна услуга што почнува да враќа нов статусен код. Секоја промена изгледа мала изолирано. Со текот на времето, тие го претвораат кодот за интеграција во збир од одбранбени закрпи чии првични претпоставки повеќе не се видливи.
Одговорот не е засекогаш да се замрзне секој интерфејс. Стабилните API се важни, но системите мора да се развиваат. Покорисната цел е да се проектира за предвидлива еволуција: да се дизајнираат граници, договори и практики за распоредување што ја прават очекуваната промена евтина, а небезбедната промена очигледна.
Претпоставете дека договорите ќе се менуваат
API-договорот е повеќе од рута и JSON пример. Тој вклучува задолжителни и опционални полиња, однесување при валидација, редослед, пагинација, автентикација, формати на грешки, ограничувања на стапката и оперативни очекувања како што се временските ограничувања. Клиентите зависат од сето ова, вклучувајќи и детали што никој не ги запишал.
Затоа „само додадовме поле“ не е автоматски безопасно. Толерантен клиент треба да ги игнорира непознатите полиња, но строг валидатор на шема, генериран модел или мапер за база на податоци може да ги отфрли. Исто така, менувањето на enum може да ги скрши потрошувачите што претпоставиле исцрпна листа на вредности.
Почнете со разликување на адитивни и промени што кршат компатибилност. Адитивните промени обично можат да коегзистираат со постојните клиенти. Промените што кршат компатибилност бараат патека за миграција.
- Додавањето опционално својство во одговорот обично е адитивно.
- Додавањето задолжително својство во барањето крши компатибилност за постојните повикувачи.
- Преименувањето или менувањето на значењето на поле крши компатибилност, дури и кога неговиот тип останува ист.
- Менувањето на стандардните вредности за пагинација може да крши компатибилност кога клиентите се потпираат на целосни множества резултати.
- Заменувањето познат одговор за грешка со генерички може да ги скрши и однесувањето на клиентот и набљудливоста.
Оваа класификација не е бирократија. Таа одредува дали е доволно нормално распоредување или на потрошувачите им е потребна намерна транзиција.
Поставете слој против корупција на рабовите
Заднинските апликации стануваат кревки кога транспортните товари течат директно во деловната логика и моделите за перзистенција. Контролер што декодира JSON, го проследува до услуга и го складира каков што е, направил надворешна претстава дел од доменот.
Наместо тоа, преведувајте на границата. Во PHP, DTO за барање или мапер за влез може да го валидира јавниот договор и да конструира команда ориентирана кон доменот. Доменот потоа работи со концепти што ги поседува, а не со какви било имиња што случајно биле избрани во HTTP товар.
final class CreateSubscriptionInput
{
public function __construct(
public readonly string $customerId,
public readonly string $planCode,
) {}
}
function mapCreateSubscription(array $payload): CreateSubscriptionInput
{
if (!isset($payload['customer_id'], $payload['plan'])) {
throw new InvalidArgumentException('customer_id and plan are required');
}
return new CreateSubscriptionInput(
customerId: (string) $payload['customer_id'],
planCode: (string) $payload['plan'],
);
}
Кога јавниот API подоцна ќе прифати plan_code, маперот може да ги поддржува двете имиња за време на транзицијата, додека услужниот слој останува непроменет. Ова е тесна, намерна одлука за компатибилност, наместо протекување што се шири низ апликацијата.
Истиот принцип важи и при користење API од трети страни. Веднаш претворете го нивниот одговор во внатрешна претстава. Не дозволувајте имињата на полињата на добавувачот, nullable вредностите или вокабуларот за статус да станат претпоставки низ целиот ваш код.
Верзионирајте го однесувањето, не само URL-адресите
Верзионирањето на URL-адреси како /v1/orders е корисно кога ви треба јасно одделен договор. Тоа не е замена за планирање на компатибилноста. Верзионирана крајна точка сè уште може да отстапува ако нејзината семантика се промени без документација, тестови или комуникација со потрошувачите.
Претпочитајте адитивна еволуција во рамките на главна верзија. Воведете ново опционално поле пред да го направите задолжително. Додадете нова крајна точка кога операцијата има суштински различно однесување. Одржувајте го старото однесување достапно доволно долго за вистинските клиенти да преминат. Резервирајте нова главна верзија за промени што не можат безбедно да коегзистираат.
Практичен процес за застарување има четири дела:
- Документирајте ја замената и точната разлика во однесувањето.
- Изложете јасен сигнал за застарување таму каде што вашите API-конвенции го поддржуваат тоа.
- Измерете ја преостанатата употреба на старата патека или поле.
- Поставете датум за отстранување само откако ќе постои веродостојна патека за миграција.
Не користете известување за застарување како замена за мониторинг. Ако не можете да утврдите дали потрошувачите сè уште користат крајна точка, нагаѓате за кршењето. Метриките за барања, структурираните логови и идентификаторите на клиентите, каде што е соодветно, му даваат на тимот докази за безбедна одлука за отстранување.
Нека базите на податоци се развиваат во чекори
Промените во шемата на базата на податоци се чест извор на API-отстапување бидејќи изданијата на апликацијата и миграциите на шемата не се случуваат секогаш во истиот миг. Безбеден образец е прошири, мигрирај, стесни.
Прво, проширете ја шемата без да ја поништите важноста на тековната апликација: додајте nullable колона, нова табела или индекс. Потоа, распоредете код што ја запишува новата претстава и може да ги чита и старите и новите форми. Пополнете ги постојните податоци наназад во контролирани серии. Дури откако новата патека ќе се воспостави, треба да наметнете ограничувања или да ја отстраните старата структура.
На пример, претворањето на единственото текстуално поле shipping_address на нарачката во структурирани колони за адреса не треба да почне со бришење на старата колона. Додајте ги новите колони, запишувајте двојно додека ги валидирате резултатите, пополнете ги постарите редови наназад, префрлете ги читањата и отстранете го наследеното поле во подоцнежно издание.
Двојните запишувања воведуваат привремена грижа за конзистентноста, затоа одржувајте го периодот краток и набљудлив. Одлучете која претстава е авторитативна, логирајте несовпаѓања и направете го однесувањето при повторување идемпотентно. Повторување по истек на временско ограничување не смее да создаде две претплати, две фактури или две противречни верзии на истиот запис.
Тестирајте ја компатибилноста како функционалност на производот
Единичните тестови ја докажуваат локалната логика; тие не докажуваат дека API останува употреблив за постојните клиенти. Додајте тестови насочени кон договорите околу границата. За API на добавувачи, проверете дека клиентот може да анализира репрезентативни одговори, вклучувајќи опционални полиња, недостасувачки полиња каде што се дозволени и непознати enum вредности. За API што ги објавувате, тестирајте ги документираните примери за барања и одговори со апликацијата што работи.
Договорите водени од потрошувачите можат да помогнат кога има повеќе внатрешни потрошувачи, но им е потребна дисциплина. Договорот треба да изразува вистинска зависност, а не да замрзне случаен детаљ од имплементацијата. Прегледувајте го исто толку внимателно како продукциски код.
Тестирајте ги и патеките на неуспех. Потврдете што се случува кога зависноста е бавна, недостапна или враќа неправилно форматирани податоци. Осигурете се дека повторувањата се ограничени, временските ограничувања се изречни и грешките се мапираат во стабилен јавен формат. Компатибилноста вклучува и како системот откажува.
Распоредувајте за повратливост
Docker и автоматизираната испорака ги прават распоредувањата повторливи, но повторливото не е автоматски и повратно. Избегнувајте изданија што бараат секој контејнер да се префрли одеднаш. За време на постепено распоредување, старите и новите инстанци на апликацијата може заедно да опслужуваат сообраќај. И двете мора да ја разбираат активната шема на базата на податоци и сите пораки што веќе се во редиците.
Функционалните знаменца се вредни кога го одделуваат распоредувањето од активирањето. Прво распоредете поддршка за нов договор, овозможете ја за ограничена публика, следете ги грешките и латентноста, а потоа проширете. Знаменцето е најкорисно кога неговата исклучена состојба сè уште е тестирана, одржлива патека — а не заборавена гранка што била скршена со месеци.
Архитектурата што ја предвидува еволуцијата не го елиминира API-отстапувањето. Таа го претвора отстапувањето од итен случај во вообичаена инженерска работа: идентификувајте го договорот, зачувајте ја компатибилноста каде што е важна, набљудувајте ја вистинската употреба и отстранете го старото однесување само кога системот е подготвен. API што најлесно се одржува не е оној што никогаш не се менува. Тоа е оној што може да се менува без секој потрошувач да се плаши од следното издание.