Испорачувајте робусни API-ја: Надминете ги рамките за трајна архитектура
Рамките се одлични за брзо поставување API во движење. Тие обезбедуваат рутирање, помошници за валидација, вбризгување зависности, миграции, редици и позната структура за нова услуга. Но рамката не може да одлучи каде припаѓаат деловните правила, што се случува кога давател на плаќања ќе истече по време, или дали промена во базата на податоци може безбедно да се распореди додека постарите инстанци на апликацијата сè уште работат.
Робусните API произлегуваат од архитектонски одлуки што остануваат разумни и по првото издание: јасни граници, експлицитно справување со неуспеси, безбедни промени на податоци и оперативни навики што го прават однесувањето разбирливо под притисок. PHP може многу добро да го поддржи овој стил на инженерство, под услов рамката да се третира како механизам за испорака, а не како самата архитектура.
Намерно одржувајте го HTTP слојот тенок
HTTP контролерот треба да преведе барање во акција на апликацијата и резултатот повторно да го преведе во HTTP одговор. Тој не треба да стане местото каде што се насобираат авторизација, ценообразување, зачувување, известувања и повици кон трети страни.
Корисна граница е да се одделат транспортните грижи од случаите на употреба на апликацијата. Контролерот го парсира и валидира влезот. Услугата за случај на употреба ја координира работата. Објектите ориентирани кон доменот ги содржат правилата што треба да останат вистинити без оглед дали повикувачот е HTTP, CLI команда, worker од редица или закажана задача.
final class CreateOrderController
{
public function __invoke(CreateOrderRequest $request, CreateOrder $useCase): JsonResponse
{
$order = $useCase->handle(
new CreateOrderInput(
customerId: $request->user()->id,
items: $request->validated('items')
)
);
return response()->json(['id' => $order->id], 201);
}
}
Ова не е архитектура заради самата архитектура. Кога ќе се променат правилата за нарачки, тие имаат едно природно место. Кога на друг интерфејс му треба да создаде нарачка, тој може повторно да го употреби истиот случај на употреба без да симулира HTTP барање. Тестовите исто така стануваат побрзи и пофокусирани бидејќи за најголемиот дел од деловното однесување не е потребен веб-сервер.
Направете ги границите видливи во кодот
„Услуга“ често е нејасна ознака. Претпочитајте имиња што ја откриваат одговорноста: CreateInvoice, CalculateTax, CustomerRepository или PaymentGateway. Важното не е да следите модерна структура на папки; туку зависностите да упатуваат во разумна насока.
Деловните правила не треба да знаат дали податоците доаѓаат од MySQL, PostgreSQL, Redis или надворешен API. Обратно, инфраструктурниот код не треба тивко да ги редефинира деловните одлуки. Адаптерот за платен портал може да знае како да испрати HTTP барање. Апликацискиот слој одлучува кога треба да се обиде плаќање и што значи одбиено плаќање за работниот тек.
Интерфејсите се највредни на вистински споеви: надворешни услуги, време, случајност, складирање датотеки или сложено зачувување. Создавањето интерфејс за секоја класа додава формалност без да ја подобри можноста за промена. Почнете со конкретен код таму каде што границата е локална, па воведете апстракција кога повеќе имплементации или изолирано тестирање ќе го оправдаат тоа.
Дизајнирајте за неуспех пред сообраќајот да ви го открие
Секоја оддалечена зависност може да не успее, да одговори доцна или да успее откако клиентот веќе се откажал од чекање. Сигурниот API го дефинира одговорот на тие услови наместо да им дозволи на стандардните поставки да одлучат.
- Поставете временски ограничувања. Појдовно барање без временско ограничување може неограничено да троши капацитет на worker-ите.
- Повторувајте селективно. Повторувајте привремени неуспеси, како грешки при поврзување или одредени серверски грешки, а не грешки при валидација или секој неуспешен одговор.
- Користете идемпотентност за барања што ја менуваат состојбата. Повторно барање од клиентот не смее да создаде две нарачки затоа што првиот одговор бил изгубен.
- Одделете ја трајната работа од непосредните одговори. Испраќајте е-пошта, генерирајте извештаи или известувајте интеграции асинхроно кога на корисникот не му е потребен резултатот веднаш.
- Запишете доволно контекст за истрага. Идентификатори за корелација, стабилни кодови за грешки и структурирани логови се покорисни од генеричко „нешто тргна наопаку“.
Редиците помагаат, но не се магичен прекинувач за сигурност. Задача во редица може да се изврши двапати, да пристигне доцна или трајно да не успее. Затоа обработувачите треба, каде што е можно, да бидат безбедни за повторување. На пример, зачувајте идентификатор на настанот од давателот пред да го примените ефектот на webhook, и отфрлете дупликати со уникатно ограничување во базата на податоци. Нека базата на податоци ја спроведува инваријантата наместо да се потпирате само на меморијата на апликацијата.
Враќајте корисни грешки без да изложувате внатрешни детали
На клиентите им требаат предвидливи грешки; на напаѓачите не им требаат stack trace-ови. Дефинирајте стабилен облик на грешка со машински читлив код, порака читлива за луѓе и незадолжителни детали за полињата. Логирајте го исклучокот и неговиот оперативен контекст внатрешно, а потоа надворешно вратете соодветен статусен код.
{
"error": {
"code": "inventory_unavailable",
"message": "One or more items are no longer available."
}
}
Ова, исто така, им дава на корисниците на API договор врз кој можат да градат. Промената на внатрешна класа за исклучок не треба да го принуди секој клиент да го промени справувањето со грешки.
Нека базата на податоци ја заштити вистината
Валидацијата во апликацијата го подобрува корисничкото искуство, но не е замена за ограничувањата во базата на податоци. Две истовремени барања можат да поминат проверка „дали постои оваа е-пошта?“ пред кое било од нив да вметне ред. Уникатен индекс правилно ја решава таа трка.
Користете надворешни клучеви кога врската е реална, ограничувања non-null за задолжителни вредности, check ограничувања каде што се поддржани и соодветни, и внимателно избрани уникатни индекси за деловни идентификатори. Завиткајте ги промените што мора заедно да успеат во трансакција, но одржувајте ги трансакциите кратки. Одржувањето отворена трансакција додека повикувате оддалечена услуга го зголемува времето на заклучување и ја отежнува дијагнозата на спорењето.
Еволуцијата на шемата заслужува иста грижа како и апликацискиот код. Безбедно распоредување најчесто следи шема на проширување и повлекување: прво додадете nullable колона или нова табела, распоредете код што може да работи со двете форми, пополнете ги податоците по потреба, префрлете ги читањата и запишувањата, а потоа отстранете ја старата структура во подоцнежно издание. Ова избегнува прекинување на инстанци што сè уште опслужуваат сообраќај за време на постепено распоредување.
Контејнерите ја стандардизираат испораката, не дизајнот
Docker ги прави локалните и распоредените средини поконзистентни, што е вредно. Тој сам по себе не прави услуга набљудлива, безбедна или скалабилна. Корисна слика на контејнер има јасна runtime команда, конфигурација доставена преку околината или механизам за тајни и не се потпира на запишлива локална состојба за трајни податоци.
За PHP апликации, разликувајте го веб-runtime-от од долготрајните worker-и. Worker за редица има потреба од стратегија за рестартирање, координација на распоредувањето и следење на меморијата; тој не е само уште една копија од HTTP процесот. Осигурете се дека worker-ите се рестартираат кога се менува апликацискиот код и овозможете грациозно гасење за задача во тек да не биде напуштена на половина од работата.
Проверките на здравјето треба да одговараат на конкретни прашања. Проверката на живост може да утврди дека процесот работи. Проверката на подготвеност може да утврди дека може да прифати сообраќај. Избегнувајте лесна endpoint-точка за живост да ја претворите во синџир повици до секоја зависност, бидејќи привремен прекин на базата на податоци може да предизвика непотребни рестартирања.
Оптимизирајте откако ќе можете да го објасните оптоварувањето
Работата на перформансите е најефективна кога започнува со конкретно прашање: кој endpoint е бавен, при каква форма на податоци и каде се троши времето? Мерете го бројот и траењето на прашањата, проверувајте планови за извршување за скапи прашања и барајте непотребна серијализација или мрежни повици пред да посегнете по кеш.
Кеширањето е компромис меѓу брзина и свежина. Кеширајте податоци со експлицитен сопственик, стратегија за клучеви, политика на истекување и план за поништување. Ако тимот не може да објасни кога вредноста станува застарена и како се коригира, кешот веројатно подоцна ќе создаде суптилен проблем со точноста.
Архитектурата е навика за зачувување опции
Целта не е совршен дијаграм на апстракции. Целта е API што може да ја прифати следната промена без секој endpoint да се претвори во ризично уредување. Одржувајте ги HTTP грижите тенки, поставете ги правилата таму каде што можат повторно да се употребат, направете го однесувањето при неуспех експлицитно, користете ја базата на податоци за зачувување на инваријантите и распоредувајте ги промените во компатибилни чекори.
Рамките ја забрзуваат испораката. Трајната архитектура ја штити брзината на испорака откако лесниот дел ќе заврши. Тоа е разликата меѓу API што само се лансира и API што продолжува да заслужува доверба додека растат неговите одговорности.