ИТ развој

Architecting APIs for Evolving Systems: Beyond the Quick Fix

Архитектура на API за системи што се развиваат: Надвор од брзото решение

API-то што го решава денешното барање за интеграција за едно попладне може тивко да стане најскапата зависност утре. Тоа не е аргумент за прекумерно инженерство. Тоа е аргумент за препознавање дека API е договор: штом друга апликација, тим или клиент се потпре на него, неговото менување станува проблем на координација наместо задача за рефакторирање.

Добрата API архитектура не се состои во предвидување на секоја идна функционалност. Таа се состои во тоа обичните промени да бидат безбедни. Најдолготрајните системи воспоставуваат јасни граници, го зачувуваат значењето и оставаат простор за еволуција без секој потрошувач да биде заробен во синхронизиран циклус на изданија.

Почнете од договорот, а не од контролерот

Вообичаено брзо решение започнува со постоечка крајна точка и ново поле: додадете колона, изложете ја во JSON, ажурирајте контролер и објавете. Тоа може да биде сосема соодветно за внатрешен прототип. Станува ризично кога полето го менува значењето на ресурсот, открива детали за складирањето или создава однесување за кое потрошувачите ќе претпостават дека е трајно.

Дефинирајте го договорот во смисла на деловни концепти. Клиентот треба да бара нарачка, статус на плаќање или достапна опција за испорака; не треба да мора да ги разбира имињата на вашите табели, ORM односите или редоследот по кој случајно се извршуваат позадинските задачи.

На пример, одговорот може да претставува стабилен облик на ресурсот, додека основната PHP апликација подоцна може да ги реорганизира услугите, редиците и табелите во базата на податоци.

{
  "id": "ord_4821",
  "status": "processing",
  "total": {
    "amount": 4999,
    "currency": "USD"
  },
  "links": {
    "self": "/orders/ord_4821"
  }
}

Ова не значи дека на секој одговор му треба хипермедија или разработен слој на апстракција. Значи дека потрошувачите треба да зависат од намерни имиња и документирана семантика, а не од случајни детали на имплементацијата, како што се order_total_cents или внатрешна enum вредност што може да биде преименувана следната недела.

Најпрво дизајнирајте за дополнителни промени

Најбезбедните API промени се дополнителни. Ново опционално поле, нова крајна точка или дополнителен филтер обично може да се воведе без да се нарушат постојните потрошувачи. Отстранувањето поле, менувањето на типот на поле, редефинирањето на вредност на статус или правењето опционално поле во барање задолжително, бара многу поголема претпазливост.

Во пракса, ова води до корисно правило: зачувајте го постојното однесување и додадете нова можност покрај него. Ако поле со низа мора да стане структурирани податоци, не му го менувајте тивко типот од низа во објект. Воведете ново поле, документирајте ги двете во преоден период и отстранете го старото само преку соопштен процес на застарување.

  • Користете стабилни идентификатори што не се поврзани со стратегијата за примарен клуч на базата на податоци.
  • Направете ја валидацијата на барањата експлицитна, вклучувајќи формати, ограничувања и дозволени вредности.
  • Враќајте предвидливи облици на грешки за клиентите да можат да разликуваат невалиден влез од привремен неуспех.
  • Документирајте ја семантиката на пагинација, подредување и филтрирање, наместо да ја оставате како стандардна поставка на контролерот.
  • Бидете претпазливи со „корисни“ стандардни вредности; недокументирана стандардна вредност сè уште е дел од договорот штом клиентите се потпрат на неа.

Верзионирајте само кога договорот навистина се разидува

Верзионирањето е корисно, но не е замена за дисциплина во компатибилноста. Создавањето нова верзија за секоја мала промена создава дуплиран код, фрагментирана документација и потрошувачи што никогаш не надградуваат. Целосното избегнување верзионирање може да им наметне промени што кршат компатибилност на клиентите без излезна опција.

Прагматичен пристап е верзијата да се одржува стабилна додека промените остануваат компатибилни, а потоа да се воведе нова главна верзија кога моделот на ресурсот или однесувањето навистина не можат чисто да коегзистираат. Конвенција заснована на рута, како /api/v1/orders, е лесна за управување и следење за многу тимови. Верзионирањето засновано на заглавија исто така може да функционира, но често ги прави отстранувањето проблеми и кеширањето помалку очигледни. Важна е доследноста и реалистичен животен циклус за старите верзии.

Застарувањето треба да биде оперативно, а не церемонијално. Идентификувајте ги активните потрошувачи каде што е можно, објавете цел за миграција, одржувајте детерминистичко однесување за време на транзицијата и поставете датум за отстранување само кога организацијата може да го поддржи тоа. Стара крајна точка што нема сопственост или план за повлекување не е наназадна компатибилност; тоа е траен долг за одржување.

Направете ги запишувањата безбедни при повторни обиди

Мрежите откажуваат на незгодни места. Клиентот може да испрати барање за креирање, да го изгуби одговорот и да се обиде повторно. Ако крајната точка создава ново плаќање, пратка или нарачка при секој повторен обид, API е технички достапно, но оперативно небезбедно.

За запишувања иницирани однадвор, разгледајте клучеви за идемпотентност. Клиентот доставува единствен клуч за логичка операција, а серверот го складира исходот поврзан со тој клуч. Повтореното барање потоа може да го врати првичниот резултат наместо повторно да го изврши дејството.

$key = $request->header('Idempotency-Key');

if (!$key) {
    return response()->json([
        'error' => [
            'code' => 'idempotency_key_required',
            'message' => 'An Idempotency-Key header is required.'
        ]
    ], 400);
}

$existing = IdempotencyRecord::where('key', $key)->first();

if ($existing) {
    return response()->json($existing->response_body, $existing->status_code);
}

Примерот е само почеток. Производствен дизајн мора и да го поврзе клучот со автентицираниот повикувач и операцијата, да обработи истовремени барања за истиот клуч, да дефинира задржување и да складира доволно податоци за да открие повторна употреба на клуч со различен товар. Во тие детали се наоѓа доверливоста.

Дозволете базата на податоци да ги спроведува важните вистини

Валидацијата на апликацијата го подобрува искуството на клиентот, но базата на податоци треба да ги заштити инваријантите што никогаш не смеат да бидат прекршени. Ако адресата на е-пошта мора да биде единствена во рамките на сметка, користете ограничување за единственост. Ако ставка од нарачка мора да припаѓа на нарачка, користете надворешен клуч таму каде што моделот на податоци го дозволува тоа. Ако транзиција на состојба бара ексклузивен пристап, моделирајте го тоа барање со трансакции, заклучување или оптимистичка конкурентност, како што е соодветно.

API-јата често откриваат проблеми со конкурентноста што биле невидливи во интерфејс за еден корисник. Два работници може да го ажурираат истиот ресурс, webhook може да пристигне додека корисникот менува поставки, а асинхрона задача може да заврши по истек на временскиот рок. Разгледајте враќање маркер за верзија, како updated_at или број на ревизија, и барајте клиентот да го достави при чувствителни ажурирања. Несовпаѓањето може да врати одговор за конфликт наместо тивко да презапише понови податоци.

Одржувајте ги заднинските слоеви корисни, а не церемонијални

PHP рамките го олеснуваат сместувањето на рутирањето, валидацијата, авторизацијата, перзистентноста и форматирањето на одговорите во еден метод на контролер. Таа погодност има краток рок на траење. Како што се множат правилата, дејствата на контролерот стануваат тешки за тестирање и лесни за кршење.

Структурата што може да се одржува обично ги одвојува HTTP-грижите од однесувањето на апликацијата. Контролерите ги преведуваат барањата и одговорите. Објектите за барања го валидираат влезот. Апликациските услуги ги координираат случаите на употреба. Кодот ориентиран кон доменот ги поседува деловните правила. Репозиториумите или услугите за пребарување може да изолираат сложена перзистентност таму каде што тоа изолирање носи вистинска вредност.

Не создавајте слоеви само затоа што дијаграмот вели дека припаѓаат таму. На мала крајна точка со едноставно пребарување не ѝ требаат шест класи. Извлечете граница кога таа заштитува правило, намалува дуплирање или прави нестабилна зависност заменлива. Архитектурата треба да ги намали трошоците за промена, а не да го зголеми бројот на датотеки потребни за да се направи една.

Распоредувајте ги промените како низа, а не како настан

Промените во шемата го заслужуваат истиот начин на размислување за компатибилност како и API промените. Распоредување што додава колона што не смее да биде null, веднаш запишува во неа и претпоставува дека секој контејнер на апликацијата го има новиот код може да не успее за време на постепени распоредувања. Побезбедниот образец е прошири, мигрирај, префрли и стесни.

  1. Додадете ја новата шема во форма компатибилна наназад.
  2. Распоредете код што може да ги чита и запишува и старите и новите претставувања.
  3. Пополнете ги постојните податоци во контролирани серии.
  4. Префрлете ги читањата на новото претставување по проверката.
  5. Отстранете ги застарениот код и шемата само по преодниот период.

Docker и автоматизираните цевководи за распоредување ја прават оваа низа повторлива, но не ја отстрануваат потребата од неа. Извршувајте миграции намерно, направете ги здравствените проверки значајни и осигурете се дека примероците на апликацијата можат накратко да коегзистираат низ верзиите на распоредувањето.

Трајното мерило на API

API е успешно кога промената останува здодевна. Потрошувачите можат да надградуваат без страв, операторите можат да дијагностицираат неуспеси од јасни сигнали, а заднинските инженери можат да ја подобруваат внатрешноста без секоја одлука за имплементација да ја изложат на надворешниот свет.

Брзото решение понекогаш е вистинскиот прв потег. Потегот на искусен инженер е да знае кои кратенки стануваат договори, а потоа да постави токму доволно структура околу нив за следната промена да биде побезбедна од претходната.

Портрет на автор на блогот

Mihajlo

Јас сум Михајло - развивач поттикнат од љубопитност, дисциплина и постојаната желба да создадам нешто значајно. Споделувам увиди, упатства и бесплатни услуги за да им помогнам на другите да ја поедностават својата работа и да растат во постојано развивачкиот свет на софтверот и вештачката интелигенција.