Кога на вашиот API му треба втор мозок: градење отпорни слоеви за интеграција
Интеграцијата со API ретко пропаѓа затоа што некој заборавил како да направи HTTP барање. Таа пропаѓа затоа што надворешниот свет не е дел од вашиот систем, а сепак вашиот производ зависи од тоа тој да се однесува како да е.
Давател на услуги за плаќање истекува по прифаќањето на наплатата. CRM враќа неочекуван облик на поле. Услуга од партнер забавува во вашиот најзафатен час. Надворешно API воведува нов статус што не го препознавате. Ако овие детали директно протекуваат во контролери и деловна логика, секоја надворешна зависност станува извор на нестабилност низ целата апликација.
Тогаш слојот за интеграција станува вториот мозок на вашето API: намерно поставена граница што ги памети деталите на протоколот, применува одбранбени правила и му дава на остатокот од апликацијата стабилен јазик за работа.
Зошто тенок HTTP клиент не е доволен
Примамливо е да ставите повик до SDK или неколку HTTP барања директно во сервисна класа и да сметате дека работата е завршена. Тој пристап е брз и понекогаш е соодветен за внатрешна алатка со низок ризик. Но интеграциите во продукција акумулираат грижи што не припаѓаат во доменскиот код: автентикација, пагинација, истекувања, повторни обиди, идемпотентност, мапирање на одговори, ограничувања на стапка, набљудливост и класификација на грешки.
Вашата услуга за нарачки треба да одлучува дали нарачката може да се исполни. Таа не треба да знае дали давателот на испорака привремен прекин го означува како 503, ги обвиткува грешките од валидација во вгнезден објект или бара клуч за идемпотентност во одредено заглавие.
Корисниот слој за интеграција претвора надворешен договор во внатрешен. Надворешната услуга може да биде неконзистентна, независно верзионирана или несовршено документирана. Во вашата апликација, повикувачите треба да добиваат предвидливи објекти, експлицитни неуспеси и однесување што одговара на вашите деловни правила.
Изградете граница против корупција
Изразот „слој против корупција“ може да звучи повеличествено отколку што е. Во пракса, тој значи да одбиете концептите специфични за добавувачот да се распространат низ кодната база.
Да претпоставиме дека една апликација треба да создава испораки. Наместо контролерите директно да повикуваат клиент за превозник, дефинирајте интерфејс според сопствени термини:
interface ShipmentGateway
{
public function createShipment(CreateShipmentRequest $request): ShipmentResult;
}
Адаптер специфичен за превозникот може да го преведе тоа барање во товарот на давателот и неговиот одговор повторно да го преведе во ShipmentResult. Остатокот од апликацијата не се грижи дали давателот враќа URL за етикета, кодиран документ или токен за анкетирање. Тој се грижи дали испораката е создадена, е во чекање или не успеала поради причина што вреди да му се прикаже на корисник.
Оваа граница исто така ги прави промените кај давателот подносливи. Замената на превозник, надградбата на SDK или додавањето резервен давател станува локализирана задача, наместо пребарување низ целото складиште за терминологија на добавувачот.
Нормализирајте ги податоците намерно
Не го пресликувајте секое поле од надворешниот одговор во вашиот внатрешен модел. Мапирајте само што ѝ е потребно на вашата апликација, зачувајте го оригиналниот товар одделно кога е корисен за поддршка или ревизија и валидирајте ги претпоставките на границата.
Надворешните податоци заслужуваат ист скептицизам како и корисничкиот внес. Сметајте дека недостасувачки полиња, променети enum вредности, невалидни датуми и null вредности се очекувани можности. Строг мапер што не успева со јасен исклучок за интеграција е многу побезбеден отколку да дозволите неисправни податоци да стигнат до неповрзана деловна логика и подоцна да не успеат без контекст.
Направете ги неуспесите дел од дизајнот
Мрежните повици не се обични повици на функции. Барањето може да не успее пред да стигне до давателот, откако давателот го обработил или додека одговорот патува назад. Тоа се суштински различни ситуации.
Започнете со користење експлицитни, ограничени истекувања. Клиент без истекување може неограничено да троши работници за време на инцидент кај зависна услуга. Одделете ги истекувањата за поврзување и за вкупното барање кога вашата HTTP библиотека го поддржува тоа, а потоа изберете вредности што одговараат на корисничкиот тек и работната редица. Задача за синхронизација во позадина може да чека подолго од барање за наплата; ниту една не треба да чека засекогаш.
Повторните обиди бараат еднакво внимание. Повторувањето на секој неуспех може да засили прекин, да ги исцрпи ограничувањата на стапката и да дуплира несакани ефекти. Повторувајте само неуспеси што веројатно се привремени, како ресетирана врска, истекување или избрана серверска грешка. Користете мало ограничување на повторни обиди и експоненцијално повлекување со случајно отстапување, за многу работници да не се обидуваат повторно во ист ритам.
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
return $client->send($request);
} catch (TransientTransportException $e) {
if ($attempt === 3) {
throw $e;
}
usleep(random_int(100_000, 300_000) * $attempt);
}
}
Овој пример ја илустрира формата, а не универзална политика. Во веб-барање, спиењето може да биде погрешен компромис; задача во редица може да биде подобро место за повторен обид. Уште поважно, повторувајте операција со несакани ефекти само кога е безбедно да го направите тоа.
Идемпотентноста е половината што недостига кај повторните обиди
Размислете за истекување додека создавате плаќање или испорака. Не можете безбедно да заклучите дека ништо не се случило. Ако давателот поддржува клучеви за идемпотентност, генерирајте стабилен клуч за деловната операција и повторно користете го при повторни обиди. Ако не поддржува, зачувајте ја сопствената состојба на операцијата и користете пребарување или усогласување на страната на давателот каде што е можно.
Целта не е само „обиди се повторно“. Целта е „обиди се повторно без да создадеш второ дејство во реалниот свет“. Таа разлика ги штити и клиентите и оперативните тимови.
Одржувајте ги синхроните патеки мали
Отпорен систем препознава кога може да ја одложи работата. Испраќањето известување, синхронизирањето запис, генерирањето документ или освежувањето оддалечени метаподатоци често не мора да се случи во рамките на барањето што го активирало.
Користете трајна редица за работа што може да биде асинхрона. Прво зачувајте ја локалната состојба, ставете ја задачата за интеграција во редица и оставете работник да ги обработува повторните обиди според јасна политика. Ова ја намалува латентноста на барањата и ја изолира достапноста со која се соочуваат корисниците од турбуленциите кај зависните услуги.
Сепак, редиците не се магија. Задачите имаат потреба од идемпотентни обработувачи, значајни ограничувања за повторни обиди, обработка на мртви писма или сличен тек за неуспеси и надзор. Задача што се обидува повторно засекогаш не е отпорна; таа е невидлив заостаток што чека да стане инцидент.
Набљудувајте ја границата, не само исклучокот
Кога интеграција не успее, корисното прашање обично не е „дали добивме исклучок?“ Туку „која зависност, операција, класа на статус и исход од повторен обид влијаат врз производот?“
Запишувајте структуриран контекст околу повиците, притоа чувајќи ги тајните и чувствителните податоци за клиентите надвор од дневниците. Корисните полиња често вклучуваат идентификатор за корелација, име на давателот, име на операцијата, траење на барањето, статус на одговорот, број на повторни обиди и безбедно ограничено резиме на грешката.
- Метриките откриваат растечка латентност, стапки на грешки и длабочина на редицата пред да пристигнат тикети за поддршка.
- Трасирањето поврзува бавно корисничко барање со конкретниот повик кон зависна услуга што е одговорен за него.
- Алармирањето треба да се фокусира на значајни симптоми, како трајни неуспеси или растечка работа што не може да се обнови, наместо на секој поединечен повторен обид.
Исто така, создадете оперативен пат за двосмисленост. Ако оддалечено дејство можеби успеало, но вашата апликација не знае, направете ја таа состојба видлива и усогласлива. „Непознато“ често е поискрено и побезбедно отколку погрешно да означите дејство како неуспешно.
Тестирајте договори и непријатни патеки
Единечните тестови треба да ги проверуваат мапирањето, класификацијата на грешки и одлуките за повторен обид без да прават живи мрежни повици. Интеграциските тестови можат да тестираат sandbox или контролирана околина кога постои, но не треба да бидат единствената заштита.
Тестовите фокусирани на договори се особено вредни: внесете репрезентативни товари од давателот во вашиот мапер, вклучувајќи недостасувачки полиња и непознати статуси. Тестирајте истекувања, дупликат испораки, делумен успех и граници на пагинација. Среќната патека обично е најмалку интересното нешто што го прави слојот за интеграција.
Во PHP, држете го транспортниот код доволно изолиран за тестовите да можат да заменат лажен gateway. Тој дизајн ја подобрува тестабилноста, но исто така ја појаснува архитектурата: доменските услуги зависат од вашиот интерфејс, додека адаптерите на давателите зависат од HTTP клиенти, SDK-и и ингеренции.
Најсмирениот код го поседува хаосот
Надворешните системи ќе се менуваат, ќе забавуваат и повремено ќе си противречат. Отпорниот слој за интеграција не ја елиминира таа неизвесност. Тој ја задржува.
Дајте му на надворешниот свет еден добро дефиниран влез во вашата апликација. Преведете го неговиот речник, ограничете ги неговите режими на неуспех, зачувајте доволно докази за истрага и направете ги повторните обиди безбедни. Вашиот деловен код станува поедноставен не затоа што интеграциите станале едноставни, туку затоа што сте ѝ дале на нивната сложеност одговорен дом.