Надвор од рамката: Архитектура на отпорни PHP апликации
Една рамка може да направи PHP-апликацијата да изгледа целосно многу пред да стане отпорна. Рутите се уредни, зависностите се инјектираат, миграциите се извршуваат и првото распоредување успева. Потоа пристигнува реалноста: надворешен API се забавува, работник во редица се рестартира среде задача, барање кон базата на податоци се соочува со податоци во продукциска големина или мала разлика во конфигурацијата прекинува контејнер.
Отпорноста е она што останува кога среќната патека престанува да биде единствената патека. Таа не е функционалност на рамката ниту единствен инфраструктурен избор. Таа е архитектонска навика: неуспесите да бидат разбирливи, нивниот опфат да биде ограничен и системот да може да се опорави без драма.
Користете ја рамката како основа, а не како граница
Современите PHP-рамки отстрануваат голем дел од случајната сложеност. Тие обезбедуваат рутирање, валидација, автентикација, редици, кеширање, апстракции за бази на податоци и разумна структура на проектот. Тоа е вредно. Грешката е да се дозволи конвенциите на рамката да станат целокупниот дизајн на апликацијата.
Деловните правила треба да бидат разбирливи без читање HTTP-контролер, ORM-модел или датотека за конфигурација на редица. Корисна насока на зависности е едноставна: механизмите за испорака повикуваат апликациски код; апликацискиот код го координира однесувањето на доменот; инфраструктурата имплементира интерфејси што ѝ се потребни на апликацијата.
На пример, случајот на употреба за потврдување на нарачка може да треба да зачува нарачка и да побара авторизација на плаќање. Контролерот треба да го претвори барањето во влез за тој случај на употреба, а не да содржи правила за цени, одлуки за трансакции и повици кон API од трета страна во еден метод.
final class ConfirmOrder
{
public function __construct(
private OrderRepository $orders,
private PaymentGateway $payments,
) {}
public function handle(ConfirmOrderCommand $command): Order
{
$order = Order::fromCommand($command);
$this->orders->save($order);
$this->payments->authorize($order->id(), $order->total());
return $order;
}
}
Точните имиња на класите се помалку важни од раздвојувањето. Апликацијата може да се тестира со лажен gateway, додека HTTP-слојот, адаптерот за базата на податоци и клиентот за плаќања можат да еволуираат независно. Ова не е аргумент за сложена церемонија во секоја CRUD-крајна точка. Тоа е аргумент сложеноста да се постави таму каде што може да се расудува за неа откако ќе пристигне.
Дизајнирајте API околу договори и неуспех
API е ветување дадено во несовршени услови. Неговите облици на одговори, статусни кодови, правила за валидација, однесување при страницирање и формат на грешки стануваат дел од производот. Третирањето на овие како споредни детали на контролерот ги прави подоцнежните промени непотребно скапи.
Почнете со експлицитна валидација на влезот, но не застанувајте тука. На клиентите им е потребно и предвидливо однесување кога ресурсите не постојат, кога барањето е во конфликт со тековната состојба и кога зависноста е привремено недостапна. Избегнувајте откривање stack traces или сурови грешки од базата на податоци. Наместо тоа, вратете стабилни и корисни информации за грешката.
{
"error": {
"code": "payment_unavailable",
"message": "Авторизацијата на плаќањето е привремено недостапна."
}
}
За операции што може да се повторат, идемпотентноста заслужува рано внимание. Клиентот може да истече по време откако серверот веќе обработил барање. Повторувањето на повикот „create payment“ не смее тивко да создаде второ задолжување. Клуч за идемпотентност, зачуван заедно со резултатот од операцијата, му дава на серверот начин да го препознае повторувањето и да го врати првичниот исход.
Тајмаутите се подеднакво важни. HTTP-клиент без тајмаут практично ѝ дава дозвола на надворешна услуга да зафаќа PHP-работник на неодредено време. Намерно изберете временски ограничувања, разликувајте грешки при поврзување од серверски грешки кога е корисно и повторувајте само неуспеси што веројатно се привремени. Повторувањето на грешка при валидација е шум; повторувањето на секој тајмаут без ограничувања може да го засили прекинот.
Одржувајте го пристапот до базата на податоци искрен
Многу проблеми со перформансите почнуваат како ORM-код со безопасен изглед. Релација вчитана во јамка се претвора во десетици или стотици барања. Широко барање работи локално, но скенира сè поголема табела во продукција. Трансакцијата се проширува за да вклучи мрежни повици и задржува заклучувања многу подолго од потребното.
Користете ORM за продуктивност, а потоа проверете го неговото однесување кога барањето е важно. Прегледајте ги генерираните барања, однапред вчитувајте ги релациите што навистина се потребни, избирајте само задолжителни колони и додавајте индекси што поддржуваат реални шеми на барања. Индексот не е значка за оптимизација; тој е структура на податоци со трошоци за запишување и складирање. Додадете го затоа што му е потребен на познато барање.
Трансакциите треба да штитат мала, кохерентна промена на состојбата. Држете ги далечинските повици надвор од трансакцијата со базата на податоци секогаш кога е можно. Ако надворешно дејство мора да следи по потврдена промена, запишете outbox настан во истата трансакција и обработете го асинхроно. Тој образец го спречува вообичаениот јаз каде што базата на податоци успешно потврдува, но подоцнежниот обид за објавување на настанот не успева.
Направете ја асинхроната работа безбедна за повторување
Редиците ја подобруваат одзивноста, но сами по себе не ја прават работата сигурна. Задачата може да се изврши повеќе од еднаш, да не успее по делумен несакан ефект или да биде повторена откако нејзините претпоставки се промениле. Затоа обработувачите на редици треба да бидат идемпотентни, набљудливи и со тесен опфат.
- Користете траен идентификатор за да откриете веќе завршена работа.
- Поставете јасна политика за повторување и насочете ги исцрпените задачи кон механизам за неуспеси што се прегледува.
- Запишувајте доволно контекст за истрага без да ставате тајни или чувствителни payload-и во логовите.
- Нека payload-ите на задачите бидат мали и вчитајте ја тековната состојба кога задачата се извршува.
Контејнерите треба да го намалуваат отстапувањето, а не да го кријат
Docker е најкорисен кога ги прави средините за развој, тестирање и распоредување поконзистентни. Сликата на контејнерот треба јасно да наведе што ѝ е потребно: PHP runtime, потребни екстензии, код на апликацијата и намерно избрана команда за стартување.
Конфигурацијата треба да доаѓа од околината, со валидација при стартување за вредностите без кои апликацијата не може да работи. Не дозволувајте недостасувачки URL на базата на податоци да произведе нејасна грешка дури по првото барање. Проверката на конфигурацијата со брзо откажување претвора оперативна мистерија во применлив неуспех при распоредување.
Изградете слики репродуктивно каде што е практично, избегнувајте да ставате алатки само за развој во продукциска слика и извршувајте ја апликацијата со најмалите привилегии што се компатибилни со нејзините потреби. Целта не е Dockerfile да изгледа визуелно софистицирано. Целта е распоредениот артефакт да биде предвидлив.
Набљудувајте го однесувањето со кое имате намера да управувате
Логовите, метриките и здравствените проверки се архитектонски алатки бидејќи обликуваат колку брзо тимот може да разбере жив систем. Структурираните логови со идентификатор на барање или корелација овозможуваат да се следи една операција низ контролери, задачи и повици кон услуги. Здравствените проверки треба да разликуваат „процесот работи“ од „апликацијата може да опслужува корисен сообраќај“, без секој привремен проблем со зависност да го претвораат во јамка за рестартирање.
Добрата набљудливост исто така ги информира дизајнерските одлуки. Ако бавна крајна точка не може да се припише на барање, промашување на кеш или далечинска зависност, нејзиното подобрување станува нагаѓање. Инструментирајте ги границите каде што се важни времето, неуспесите и употребата на ресурси.
Отпорноста е дисциплинирана едноставност
Најсилните PHP-апликации ретко се оние со најмногу обрасци. Тоа се оние чии граници се јасни, чии операции можат безбедно да не успеат и чие однесување останува разбирливо под притисок.
Прво изградете ја едноставната верзија, а потоа зајакнете ги местата каде што се менуваат податоци, каде што надворешните системи интеракцираат и каде што стануваат можни повторувања. Држете ја деловната логика доволно независна за тестирање, работата со базата на податоци доволно намерна за скалирање и претпоставките за распоредување доволно експлицитни за проверка. Рамката може да ја забрза таа работа. Архитектурата е она што осигурува апликацијата да продолжи да ја заслужува таа брзина по првото издание.