Престанете да дебагирате API-ја, почнете да ги архитектурирате за предвидливост
Повеќето неуспеси на API не започнуваат со расипана линија код. Тие започнуваат со нејасен договор, двосмислена промена на состојба или оперативна претпоставка што никогаш не била запишана. До моментот кога грешката ќе стигне до клиентот, тимот често е длабоко во логови, циклуси за повторување и прашања до базата на податоци — дебагирајќи симптоми создадени многу порано во дизајнот.
Предвидливите системи не се системи што никогаш не откажуваат. Тие се системи чии неуспеси се разбирливи, ограничени и безбедни за обработка. Таа разлика го менува начинот на кој се дизајнира бекендот: од именувањето на крајните точки и ограничувањата во базата на податоци до конфигурацијата на Docker и однесувањето при распоредување.
Предвидливоста е функционалност на производот
API е ветување меѓу системи. Клиентите треба да знаат што значи едно барање, како изгледа успехот, кои грешки се можни и дали е безбедно да се обидат повторно. Ако кој било од тие одговори зависи од скриени детали на имплементацијата, секоја интеграција станува вежба во дебагирање.
Размислете за операција слична на плаќање што создава нарачка. Може да настане истек на времето откако серверот ја зачувал нарачката, но пред клиентот да го прими одговорот. Ако клиентот повтори обично барање POST, системот може да создаде две нарачки. Непосредниот проблем изгледа како мрежен проблем. Вистинскиот проблем е што операцијата не била дизајнирана да толерира неизвесност.
За операции што може да се повторат, користете клуч за идемпотентност и направете го дел од договорот. Зачувајте го клучот со резултатот од завршената операција, а потоа вратете го истиот резултат кога истиот клуч повторно ќе биде поднесен.
$key = $request->header('Idempotency-Key');
if (!$key) {
return response()->json([
'error' => 'idempotency_key_required'
], 400);
}
$existing = OrderRequest::where('key', $key)->first();
if ($existing) {
return response()->json($existing->response_body, $existing->status_code);
}
Точната имплементација варира, но принципот не: повторните обиди не смеат да претворат привремена неизвесност во дупликатни деловни дејства.
Направете ги договорите експлицитни на границите
Бекенд-кодот може да остане флексибилен внатрешно. Јавните граници треба да бидат строги. Валидирајте го влезот рано, нормализирајте го еднаш и вратете конзистентна структура на одговор. Клиентот не треба да мора да заклучува дали грешката е низа, низа од вредности или делумно прикажана HTML-страница врз основа на тоа кој middleware го обработил исклучокот.
Корисен одговор за грешка прави разлика меѓу неуспеси при валидација, неуспеси при автентикација, ресурси што недостасуваат, конфликти и неочекувани грешки на серверот. Треба да изложи доволно информации за клиентот да одговори правилно, без да открива stack trace-ови, SQL-фрагменти или внатрешна топологија.
{
"error": {
"code": "email_already_registered",
"message": "An account already exists for this email.",
"details": {
"field": "email"
}
}
}
Конзистентноста е поважна од досетливоста. Ако една крајна точка пријавува 409 Conflict за дупликатен ресурс, додека друга враќа 422 Unprocessable Content, клиентите мора да ги учат вашите исклучоци наместо вашите правила. Изберете конвенции, документирајте ги и применувајте ги низ целото API.
Нека базата на податоци ја наметнува вистината
Проверките на ниво на апликација го подобруваат корисничкото искуство, но не се доволни за исправност. Две барања може да ја поминат истата проверка „дали постои овој запис?“ пред кое било од нив да запише во базата на податоци. Без ограничување во базата на податоци, услов на трка може тивко да произведе невалидни податоци.
Ако е-поштата мора да биде единствена, создадете ограничување за единственост. Ако подреден запис мора да референцира родителски, користете странски клуч кога тоа одговара на оперативниот модел. Ако вредност мора да биде присутна, наметнете го тоа со колона што не дозволува null. Базата на податоци е конечниот авторитет бидејќи ги гледа конкурентните запишувања од секој процес на апликацијата.
Ова го менува и обработувањето на неуспесите. Третирајте го прекршувањето на единственост како нормален конфликт што може да се случи при конкурентност, а не како невозможен исклучок. Апликацијата може прво да валидира за пријателски одговор, а потоа да го преведе неуспехот на ограничувањето во базата на податоци во истата стабилна API-грешка ако се случи трка.
Трансакциите треба да заштитуваат деловна одлука
Користете трансакција кога неколку запишувања претставуваат една одлука: резервирајте залиха, создадете нарачка и запишете ги нејзините ставки; или создадете сметка и доделете ги потребните улоги. Не користете трансакции само затоа што повеќе прашања случајно се едно до друго.
Одржувајте ги трансакциите кратки. Избегнувајте повикување оддалечени услуги, испраќање е-пошта или чекање редици додека трансакцијата е отворена. Тие дејства воведуваат бавни, ненадежни зависности додека се држат заклучувања. Прво потврдете ја авторитативната состојба, а потоа објавете ја дополнителната работа преку надежен механизам соодветен за системот.
Дизајнирајте ги патеките на неуспех пред среќната патека
Секоја надворешна зависност може да стане бавна, недостапна или неконзистентна. Предвидливата услуга однапред одлучува што се случува тогаш. Дали барањето брзо откажува? Дали враќа кеширани податоци? Дали работата се става во редица за подоцна? Дали повикувачот може безбедно да се обиде повторно?
Повторните обиди заслужуваат особено внимание. Повторувањето на секој неуспех може да засили прекин така што го умножува сообраќајот кон услуга што веќе не е здрава. Повторувајте само грешки што веројатно се привремени, ограничете го бројот на обиди, користете постепено зголемување на интервалите и поставете истекувања на времето. Барање без истек на времето не е отпорно; едноставно е подготвено да чека неограничено.
- Поставете истекувања за поврзување и одговор за излезните HTTP-повици.
- Повторувајте само кога операцијата е безбедна или заштитена со идемпотентност.
- Користете ограничени редици и јасно обработување на неуспеси за работите во заднина.
- Вклучете идентификатори за корелација во логовите и одговорите каде што е соодветно.
- Враќајте грешки што овозможуваат дејствување, наместо да ги принудувате клиентите да нагаѓаат.
Набљудливоста припаѓа во дизајнот, а не во ретроспективата по инцидент. Структурираните логови треба да ги идентификуваат барањето, релевантните идентификатори на ресурси, исходот и времетраењето. Метриките треба да овозможат разликување меѓу зголемен сообраќај и зголемени неуспеси. Трасирањето може да ги разјасни синџирите на зависности, но дури и скромен систем има огромна корист од конзистентни идентификатори на барања и значајни логови.
Направете го однесувањето при распоредување досадно
Docker може да ги направи локалните и распоредените околини поконзистентни, но само ако конфигурацијата е намерна. Сликата треба да ја содржи апликацијата и нејзините зависности за извршување. Вредностите специфични за околината — акредитиви за базата на податоци, URL-адреси на услуги, ознаки за функционалности и поставки за логови — треба да се вбризгуваат при извршување, наместо да се вградени во сликата.
Стартувањето исто така мора да биде предвидливо. Ако контејнер претпоставува дека базата на податоци е подготвена само затоа што процесот започнал, распоредувањата повремено ќе откажуваат. Подготвеноста не е исто што и создавање процес. Изградете експлицитно однесување за повторување околу зависностите при стартување, со ограничувања и корисни логови, и осигурете се апликацијата јасно да откаже кога не може да стане подготвена.
Миграциите на шемата ја бараат истата дисциплина. Расоредување не треба да претпоставува дека сите активни примероци на апликацијата истовремено ги менуваат верзиите. Дајте предност на компатибилни промени: додајте колона што дозволува null пред да ја направите задолжителна, распоредете код што може да ги обработи двете структури, пополнете ги податоците ако е потребно, а потоа наметнете го построгото ограничување во подоцнежна промена.
Оптимизирајте ги прашањата што ги поставувате
Работата на перформансите станува помирна кога започнува со однесување, а не со нагаѓање. Прашајте кое барање е бавно, кое прашање е скапо, која зависност доминира во латентноста и дали работата е неопходна. Потоа измерете го одговорот во репрезентативна околина.
Вообичаените неефикасности во бекендот ретко се егзотични: неограничени множества на резултати, повторени прашања во циклуси, индекси што недостасуваат за вистинските шеми на пристап, непотребна серијализација и оддалечени повици на критични патеки на барања. Пагинацијата, селективните полиња, eager loading каде што е соодветно и индексите усогласени со вистинските прашања често даваат поголема вредност од предвременото кеширање.
Кеширањето е корисно кога застарените податоци се прифатливи и поништувањето е разбрано. Опасно е кога се додава како општ одговор на бавно прашање што не е испитано. Кешот може да скрие проблем во дизајнот, додека додава втор извор на вистина.
Архитектирајте за следната личност што ќе го чита кодот
Одржливоста е предвидливост со текот на времето. Јасните имиња, малите модули, стабилните граници и фокусираните тестови го намалуваат бројот на претпоставки потребни за безбедно менување на системот. Целта не е максимална апстракција. Таа е код во кој следниот инженер може да го најде деловното правило, да ги разбере неговите влезови и да види како се обработува неуспехот.
Престанете да го третирате дебагирањето како примарен одговор на ненадежен софтвер. Дебагирањето секогаш ќе биде важно, но треба да биде последната алатка, а не стандардниот оперативен модел. Кога договорите се експлицитни, интегритетот на податоците е наметнат, неуспесите се дизајнирани, а распоредувањата се намерно здодевни, API станува полесно за управување и полесно за доверба. Тоа е она што го купува добрата архитектура: помалку мистерии, побрзи одлуки и систем што се однесува како да го мисли она што го кажува.