Надвор од CRUD: Архитектирање PHP услуги за истрајност
CRUD е местото каде што почнуваат повеќето backend услуги, а не местото каде што се докажуваат. Создавањето, читањето, ажурирањето и бришењето записи можат да демонстрираат рамка, но продукциските системи се оценуваат според потешки прашања: Што се случува кога зависноста ќе заглави? Кога клиентот ќе се обиде повторно? Кога две барања го менуваат истиот ресурс? Кога распоредувањето ќе промени претпоставка што никој не ја запишал?
Издржлива PHP услуга не е онаа со најмногу слоеви. Таа е услуга чие важно однесување останува разбирливо под оптоварување, делумен неуспех, промени и рутинско одржување. Архитектурата треба да го прави среќниот пат јасен, но несреќните патишта треба да ги прави намерни.
Почнете со граници, не со папки
Позната структура на директориуми може да создаде привид на архитектура без да обезбеди вистинско раздвојување. Покорисното прашање е: кој код смее да знае за кој детаљ?
HTTP контролерот треба да преведе влезно барање во апликациски случај на употреба и неговиот резултат да го преведе во одговор. Не треба да одлучува за барања кон базата, да пресметува деловни правила или незабележливо да повикува API од трета страна. Исто така, одлуките во доменот не треба да зависат од објекти за барања, ORM модели или помошници од рамката.
Практичната услуга често има три широки области:
- Транспорт: контролери, валидација на барања, middleware за автентикација и форматирање одговори.
- Апликација: случаи на употреба што ја координираат работата, ги дефинираат границите на трансакциите и ги спроведуваат правилата на работниот тек.
- Инфраструктура: складишта за бази на податоци, редици, кешови, даватели на е-пошта и надворешни HTTP клиенти.
Ова не е аргумент за церемонијални апстракции околу секоја класа. Интерфејс за складиште вреди кога ја штити значајната апликациска логика од детали за перзистентноста или овозможува корисна алтернативна имплементација. Обвивањето на еден ORM повик само затоа што „складиштата се почисти“ често додава индирекција без да го намали ризикот.
Направете ги промените на состојбата експлицитни и трансакциски
Многу грешки се појавуваат кога услугата третира деловна операција како низа неповрзани запишувања. Разгледајте правење нарачка: резервирање залиха, создавање нарачка и евидентирање на ревизорски настан. Ако залихата е намалена, но создавањето нарачка не успее, системот создал состојба што не може лесно да ја објасни.
Ставете ги промените што мора да успеат или да не успеат заедно во трансакција на базата на податоци. Одржувајте ја таа трансакција мала: заклучете само што е потребно, избегнувајте мрежни повици додека е отворена и вратете резултат што ја претставува завршената состојба.
final class PlaceOrder
{
public function __construct(
private Connection $db,
private Inventory $inventory,
private Orders $orders,
private Outbox $outbox,
) {}
public function handle(PlaceOrderCommand $command): Order
{
return $this->db->transaction(function () use ($command): Order {
$this->inventory->reserve($command->productId, $command->quantity);
$order = $this->orders->create(
customerId: $command->customerId,
productId: $command->productId,
quantity: $command->quantity,
);
$this->outbox->record('order.placed', ['order_id' => $order->id]);
return $order;
});
}
}
Записот во outbox е важен. Испраќањето порака директно до брокер по потврдувањето може да не успее, оставајќи ја нарачката зачувана, но системите надолу по текот неинформирани. Испраќањето пред потврдувањето може да објави настан за трансакција што подоцна се поништува. Запишувањето на настанот заедно со промената во базата му дава на worker-от сигурна ставка што може да ја објави подоцна. Worker-от сепак мора да толерира дупликат објавување, но системот има пат за опоравување.
Дизајнирајте API-и за повторни обиди и несогласување
Мрежите се ненадежни на вообичаени начини. Клиентот може да добие истекување на времето откако серверот успешно ќе создаде ресурс. Ако клиентот повтори неидемпотентно барање, услугата може да создаде две нарачки, плаќања или известувања.
За операции каде дуплирањето е штетно, прифатете клуч за идемпотентност и зачувајте го со завршениот резултат. Повторувањето на истиот клуч треба да го врати првичниот исход наместо повторно да ја изврши операцијата. Дефинирајте што се случува ако истиот клуч се користи повторно со различен влез: обично отфрлете го, бидејќи тивкото прифаќање на спротивставена намера ги прави инцидентите со поддршката многу потешки за решавање.
Конкурентноста заслужува еднакво внимание. Низа „провери па ажурирај“ може да продаде повеќе залиха отколку што постои ако две барања обете ја забележат залихата пред некое од нив да запише. Користете ја базата како авторитет за конкурентност: соодветно заклучување на ред, атомско условно ажурирање или оптимистичка проверка на верзија може да го направи правилото применливо. Правилниот избор зависи од конкуренцијата и цената на повторниот обид, но потпирањето на времето на апликацијата воопшто не е избор.
Грешките се дел од договорот
Услугата треба да разликува невалиден влез, ресурси што недостасуваат, забранети дејства, конфликти и привремени неуспеси на зависности. Клиентите не можат да се однесуваат добро ако секој проблем стане генеричка серверска грешка.
Одржувајте ги одговорите за грешки стабилни и безбедни. Вратете машински читлив код, човечки читлива порака соодветна за повикувачот и идентификатор на барањето кога е достапен. Внатрешниот контекст логирајте го одделно. Деталите за базата, stack trace-овите, акредитивите и телата на одговорите од трети страни генерално не припаѓаат никаде блиску до јавен API одговор.
Користете асинхрона работа без да ја криете одговорноста
Редиците се одлични за работа што не мора да заврши пред HTTP одговорот: е-пошта, обработка на слики, испорака на webhook, индексирање за пребарување и известувања со fan-out. Тие не се замена за одлучување што му се ветува на корисникот.
Ако endpoint врати успех пред да заврши работата во редицата, неговиот договор треба да каже дека барањето е прифатено, а не дека се случил секој ефект надолу по текот. Задачите имаат потреба од јасно однесување при повторни обиди: класифицирајте ги привремените неуспеси, користете ограничени повторни обиди со одложување и испратете ги исцрпените задачи некаде каде операторите можат да ги прегледаат. Редица што бесконечно повторува трајна грешка при валидација не е отпорна; таа е само бучна.
И потрошувачите треба да бидат идемпотентни. Испораката може да се случи повеќе од еднаш, особено околу падови на worker-и и времето на потврдување. Зачувајте идентификатор на обработена порака или направете го резултирачкото запишување природно безбедно за повторување.
Одржувајте го PHP оперативно едноставен
PHP услугите имаат корист од едноставни конвенции за распоредување. Изградете еден непроменлив артефакт на апликацијата, обезбедете конфигурација преку околината или управуван систем за конфигурација, извршувајте миграции на базата како контролиран чекор при распоредувањето и направете го процесот набљудлив. Контејнерите помагаат кога ги прават локалните, тестните и продукциските околини поконзистентни; тие не ја отстрануваат потребата да се управува со конекциите кон базата, дозволите за датотеки, истекувањата на времето или уредното гасење.
За HTTP клиентите, поставете експлицитни истекувања на времето за поврзување и за целокупното барање. Зависност што виси не треба бесконечно да троши PHP worker-и. Повторно користете конекции кога runtime-от и клиентот го поддржуваат тоа, но поставете ограничувања за конкурентноста кон надворешните системи за провајдер кој има проблеми да не ја вовлече целата услуга во каскада.
Работата на перформансите треба да следи по мерењето. N+1 барања, индекси што недостасуваат, предимензионирани payload-и и неограничена пагинација се вообичаени затоа што лесно се воведуваат и тешко се забележуваат во мали збирки на податоци. Додајте видливост на барањата, дефинирајте ограничувања за пагинацијата и испитајте ја бавната патека пред да посегнете по кеш. Кеширањето помага само кога се разбрани неговите правила за невалидирање и застареност.
Градете за следната промена
Одржливоста главно е способност да се промени едно правило без да се погодува кое неповрзано однесување ќе се расипе. Именувајте ги случаите на употреба според деловните дејства, одржувајте ја конфигурацијата централизирана, тестирајте го однесувањето на границите и направете ја набљудливоста функционалност наместо додаток за итни случаи. Структурираните логови, здравствените проверки, метриките и трасите треба да одговорат што се случило без да бараат shell сесија во продукција.
Целта не е совршен архитектонски дијаграм. Таа е услуга што може да апсорбира вообичаен неуспех, искрено да ја соопшти својата состојба и да еволуира без секое мало барање да го претвора во ризична експедиција. CRUD може да биде површината на системот. Истрајноста е тоа што го прави вреден за извршување.