Pragmatic PHP: Градење системи што го издржуваат тестот на времето
Највредните бекенд системи ретко се оние со најмодерен технолошки стек. Тоа се оние што остануваат разбирливи кога барањата се менуваат, сообраќајот расте нерамномерно, некоја зависност откажува и нов програмер треба безбедно да направи промена во петок попладне.
PHP е добро приспособен за ваква работа кога се третира како практична инженерска алатка, а не како збир на кратенки. Неговата сила не е во тоа што ги отстранува тешките одлуки. Неговата сила е во тоа што им овозможува на тимовите јасно да ги изразат тие одлуки, да испорачуваат корисен софтвер и да продолжат да го подобруваат без секоја апликација да ја претворат во архитектонски експеримент.
Почнете со едноставни граници
На PHP апликација што лесно се одржува ѝ требаат јасни граници пред да ѝ требаат сложени обрасци. Обработката на HTTP, правилата на апликацијата, перзистенцијата и надворешните интеграции треба да имаат различни одговорности. За ова не е потребна голема рамка или структура на директориуми што личи на учебник. Потребно е да се одговори на едно едноставно прашање: каде треба да живее оваа одлука?
Контролерот треба да преведе барање во дејство на апликацијата и да го преведе резултатот назад во одговор. Не треба да содржи правила за цени, да гради SQL низи или да одлучува како услуга од трета страна повторува обиди. Тие детали стануваат тешки за тестирање и уште потешки за повторна употреба.
final class CreateOrderController
{
public function __invoke(CreateOrderRequest $request): JsonResponse
{
$order = $this->orders->create(
new CreateOrderCommand(
customerId: $request->customerId(),
items: $request->items()
)
);
return new JsonResponse([
'id' => $order->id(),
'status' => $order->status(),
], 201);
}
}
Апликациската услуга може да ја координира работата: да ги валидира деловните услови, да отвори трансакција каде што е потребно, да ја зачува нарачката и да побара последователно дејство. Репозиториумите треба да се занимаваат со прашања за складирање. Клиентот за интеграција треба да знае како да го повика надворешниот свет. Секој слој останува доволно мал за да може да се расудува за него кога нешто ќе тргне наопаку.
Дизајнирајте API-ја за промени, не само за денес
API е ветување. Штом мобилните клиенти, партнерите или друга внатрешна услуга зависат од обликот на неговиот одговор, неговото менување станува проблем на координација. Практичниот одговор не е превентивно да се верзионира секоја крајна точка. Тоа е договорите да бидат намерни уште од почетокот.
- Користете стабилни имиња на ресурси и предвидливи HTTP статусни кодови.
- Валидирајте го влезот на границата и вратете структурирани, применливи грешки.
- Чувајте ги внатрешните полиња на базата на податоци одвоени од јавните полиња на одговорот.
- Направете ги операциите за запишување безбедни за повторување кога клиентите може да ги повторат барањата.
- Експлицитно документирајте го однесувањето на пагинацијата, филтрирањето, подредувањето и авторизацијата.
Идемпотентноста заслужува посебно внимание. Барање POST поврзано со плаќање може да се повтори по истек на времето, дури и ако серверот веќе ја завршил работата. Клуч за идемпотентност зачуван со операцијата му овозможува на серверот да го врати првичниот исход наместо да создаде дупликат наплата или нарачка. Тоа не е граничен случај; тоа е нормално однесување на дистрибуирани системи.
Слично, избегнувајте идентификаторите на базата на податоци да бидат целиот ваш API дизајн. Јавниот идентификатор може да биде непрозрачен, додека внатрешните нумерички клучеви остануваат ефикасни за спојувања. Ова му дава простор на моделот за складирање да се развива и го намалува случајното откривање на детали од имплементацијата.
Нека базата на податоци го направи својот дел
Многу проблеми со перформансите и точноста почнуваат кога апликацијата ја третира базата на податоци како пасивно складиште. Релациската база на податоци може да наметне уникатност, релации со надворешни клучеви, трансакции и корисни индекси. Дозволете ѝ.
На пример, ако адресата на е-пошта мора да биде уникатна, валидацијата на ниво на апликација е корисна за пријателска порака за грешка, но не е доволна. Две истовремени барања може и двете да ја поминат таа валидација пред некое од нив да вметне ред. Уникатното ограничување е конечниот авторитет; апликацијата треба да го фати неговото прекршување и да го мапира во корисен одговор.
CREATE TABLE users (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL,
CONSTRAINT users_email_unique UNIQUE (email)
);
Индексите треба да следат реални обрасци на пребарување. Ако вообичаено пребарување избира неодамнешни нарачки за еден клиент, индекс што почнува со customer_id, а потоа со created_at може да биде соодветен. Додавањето индекси наслепо може да ги забави запишувањата и да троши меморија, затоа проверувајте ги плановите за пребарување и мерете ги пребарувањата што се важни.
Трансакциите треба да заштитат кохерентна деловна промена, а не само да го обвиткаат секој повик до базата на податоци. Одржувајте ги кратки. Не држете трансакција отворена додека чекате HTTP барање, испраќате е-пошта или обработувате голема датотека. Прво зачувајте ја трајната состојба, потоа асинхроно обработете ги бавните или несигурни споредни ефекти кога архитектурата го поддржува тоа.
Користете Docker за да ги намалите изненадувањата
Контејнерите се најкорисни кога локалниот развој, тестирањето и распоредувањето ги прават доследни. PHP апликацијата обично има корист од мала, експлицитна слика: фиксирајте ја верзијата на PHP, инсталирајте ги само потребните екстензии, копирајте ги манифестите на зависности пред кодот на апликацијата за да го подобрите кеширањето при градење и извршувајте Composer на повторлив начин.
Конфигурацијата треба да влегува преку поставки специфични за околината, а не преку измени на изворните датотеки. На тајните им е потребен безбеден механизам при распоредување, наместо да бидат вградени во сликите или предадени во репозиториум. Контејнерот исто така треба да има една јасна задача: да ја опслужува апликацијата, да извршува работник или да извршува закажана команда. Комбинирањето на секој процес во еден контејнер ги прави дефектите и скалирањето помалку јасни.
Docker не ја отстранува оперативната одговорност. Проверките на здравјето мора да одразуваат дали апликацијата навистина може да опслужува корисен сообраќај, логовите треба да одат на стандарден излез и стандарден излез за грешки во структуриран облик кога е можно, а трајните податоци од базата на податоци мора да живеат надвор од привремен контејнер на апликацијата.
Оптимизирајте ја патеката за која можете да докажете дека е бавна
Работата на перформансите е најефикасна кога почнува со докази. Бавна крајна точка може да биде предизвикана од неиндексирано пребарување, повторени пребарувања во јамка, преголеми серилизирани одговори, повик до оддалечено API или недоволен капацитет на PHP работници. За овие причини се потребни различни поправки.
Класичниот ORM проблем е пребарувањето N+1: вчитувате листа записи, а потоа мрзеливо вчитувате поврзан запис за секоја ставка. Кодот се чита природно, но страница со педесет нарачки може да стане педесет и едно пребарување во базата на податоци. Нетрпеливото вчитување, насочено спојување или наменско пребарување за читање може драматично да ја намали таа работа без да воведе кеш.
Кеширањето е вредно кога податоците се скапи за пресметување и доволно стабилни, но создава одговорности за невалидирање и доследност. Кеширајте јасно дефиниран резултат со познат рок на траење. Вклучете ги релевантните димензии во клучот на кешот, како што се локалитетот или опсегот на сметката. Најважно, одржувајте го системот точен кога кешот е празен, застарен или недостапен.
Направете го неуспехот првокласен влез за дизајнот
Мрежите откажуваат, редиците доцнат, базите на податоци одбиваат врски, а распоредувањата повремено откриваат занемарена претпоставка. Робусните PHP системи разликуваат грешки што треба повторно да се пробаат, грешки што треба да се пријават на повикувачот и грешки што бараат човечко внимание.
Повторните обиди бараат ограничувања и внимание. Повторувањето на привремен неуспех на врска може да биде разумно; повторувањето на грешка при валидација е залудно. За надворешни барања, користете временски ограничувања, ограничени повторни обиди и одложување. Осигурете се дека повторниот обид не може да повтори неповратно дејство, освен ако услугата примач обезбедува механизам за идемпотентност.
Набљудливоста ги претвора нејасните инциденти во решливи проблеми. Логирајте значаен контекст без да откривате тајни или лични податоци. Прикачете идентификатор на барање или корелациски идентификатор низ HTTP повици и задачи во редица. Следете исклучоци, времиња на одговор, неуспеси на редици и оптоварување на базата на податоци. Целта не се повеќе контролни табли; туку доволно докази за да се објасни што се случило.
Изберете го наједноставниот дизајн што ги зачувува опциите
Прагматичното инженерство не е антиархитектура. Тоа е архитектура со јасен модел на трошоци. Модуларен монолит со добри граници често е полесен за распоредување, дебагирање и развивање отколку збир од услуги што мора да се координираат преку мрежата. Извлечете услуга кога независното распоредување, скалирање, сопственост или потребите за доверливост го оправдуваат нејзиниот оперативен трошок.
Истата проценка важи и за апстракциите. Воведете интерфејс кога постои вистинска граница, како обработка на плаќања или складирање датотеки, а не затоа што секоја класа можеби еден ден ќе има две имплементации. Претпочитајте код што ги прави денешните правила очигледни, додека ги остава можни утрешните промени.
Системите траат кога нивните тимови можат да ги разберат под притисок. Јасните договори, доверливите ограничувања на податоците, намерното справување со неуспеси, измерената работа на перформансите и умерената архитектура ја создаваат таа предност. PHP може прекрасно да го поддржи сето тоа кога водечкото начело е едноставно: изградете го она што го решава вистинскиот проблем, а потоа оставете го полесно за промена отколку што сте го нашле.