Надвор од стекот: Архитектирање на PHP бекенди за континуирана еволуција
PHP backend-от ретко откажува затоа што тимот ја избрал „погрешната“ рамка. Откажува кога кодната база тивко станува потешка за менување отколку што бизнисот е за разбирање. Новите барања пристигнуваат како мали барања: додајте давател на плаќања, изложете крајна точка, зачувајте повеќе ревизорски податоци, поддржете втора клиентска апликација. Месеци подоцна, тие барања оставиле апликациска логика расфрлана низ контролери, ORM модели, задачи во редици и тригери во базата на податоци.
Континуираната еволуција е вистинското архитектонско барање. Целта не е да се предвиди секоја идна функционалност. Таа е да се создадат јасни граници, безбедни патишта за промени и доволно оперативна дисциплина, така што следната промена ќе остане вообичаена работа, наместо ризична експедиција.
Почнете со граници, не со апстракции
Повеќето PHP апликации почнуваат разумно: рутите повикуваат контролери, контролерите повикуваат сервиси, сервисите зачувуваат модели. Проблемот почнува кога тие слоеви стануваат ознаки наместо граници. Контролер што пресметува цени, испраќа е-пошта, запишува во неколку табели и повикува API од трета страна може и понатаму да биде „тенок“ според должината на датотеката, но тој поседува премногу одлуки.
Поиздржлива структура е кодот да се организира околу деловни способности. На пример, работниот тек за нарачка може да има апликациски сервис што координира случај на употреба, доменски објекти што изразуваат правила и инфраструктурни адаптери за перзистенција или далечински API-ја. Имињата се помалку важни од насоката на зависност: деловните правила не треба да знаат дали податоците доаѓаат од MySQL, Redis или HTTP клиент.
final class PlaceOrder
{
public function __construct(
private OrderRepository $orders,
private PaymentGateway $payments,
) {}
public function handle(PlaceOrderRequest $request): Order
{
$order = Order::fromItems($request->items());
$this->payments->authorize($order->total(), $request->paymentToken());
$this->orders->save($order);
return $order;
}
}
Ова не е аргумент за сложено доменско моделирање насекаде. На крајна точка за извештаи можеби ѝ е потребен само фокусиран сервис за пребарувања. Корисната разлика е помеѓу код што претставува деловна одлука и код што извршува технички детаљ. Држете ги одделени таму каде што промените се веројатни, скапи или регулирани.
Дизајнирајте API-ја како договори што можат добро да стареат
API не е само транспортен слој. Штом друг систем зависи од него, тој станува договор за производ. На тој договор му требаат намерни стандардни поставки: стабилни идентификатори, предвидливи облици на грешки, пагинација за колекции и експлицитна валидација.
Верзионирањето понекогаш е неопходно, но не треба да биде првиот одговор на секое дополнување. Додавањето незадолжително поле во одговорот обично е компатибилно. Преименувањето поле, менувањето на неговото значење или претворањето синхрона операција во асинхрона не е. Третирајте ги некомпатибилните промени како миграции: документирајте ги, поддржете преоден период каде што е практично и мерете го усвојувањето од потрошувачите пред да го отстраните старото однесување.
Нека семантиката на неуспех биде дел од дизајнот
Клиентите треба да разликуваат невалиден влез од недостапен ресурс, конфликт и привремен неуспех. Исто така, треба безбедно да се обидуваат повторно. За операции како создавање плаќање или обезбедување сметка, прифатете клуч за идемпотентност и зачувајте го резултатот поврзан со него. Истек на мрежното време не треба да претвори едно дејство на корисник во два записа.
За надворешни интеграции, претпоставете дека неуспесите се нормални. Поставете временски ограничувања за поврзување и вкупно извршување, внимателно класифицирајте ги грешките што можат повторно да се обидат и користете ограничен број обиди со постепено зголемување на чекањето. Повторното обидување по грешка при валидација троши ресурси; повторното обидување по привремен неуспех на платежен посредник може да биде соодветно. Ако работата може да се одложи, ставете ја во издржлива редица и направете го и работникот идемпотентен.
Нека базата на податоци го заштити она што е важно
Валидацијата во апликацијата го подобрува корисничкото искуство, но не може да биде единствената линија на одбрана. Истовремени барања, задачи во позадина, увози и административни скрипти можат да ја заобиколат проверката на ниво на контролер. Ограничувањата во базата на податоци ги заштитуваат инваријантите во точката каде што податоците стануваат трајни.
- Користете странски клучеви кога релациите мора да останат валидни.
- Користете уникатни ограничувања за идентитети и правила за дедупликација.
- Користете трансакции за промени што мора да успеат или не успеат заедно.
- Додавајте индекси врз основа на вистинските шеми на пребарување, а не по навика според колоните во табелата.
Промените на шемата ја заслужуваат истата претпазливост како и промените на кодот. Претпочитајте адитивни миграции: додајте nullable колона, распоредете код што запишува во неа, пополнете ги податоците во контролирани серии, па потоа наметнете построги ограничувања кога податоците се подготвени. Избегнувајте распоредување што претпоставува дека миграцијата е завршена насекаде во истиот точен момент. Кај постепените распоредувања, старите и новите верзии на апликацијата може да работат една покрај друга.
ORM-ите можат да ја забрзаат секојдневната перзистенција, но не го елиминираат однесувањето на базата на податоци. Прегледувајте ги генерираните пребарувања, внимавајте на случајни пребарувања по ред и користете експлицитни спојувања, eager loading или наменски пребарувања за читање кога тоа го бара шемата на пристап. Чист објектен модел е вреден; непроверен план за пребарување и понатаму е ризик во продукција.
Спакувајте го извршното опкружување, држете ја конфигурацијата надвор од него
Docker е најкорисен кога ги прави опкружувањата повторливи. Сликата на контејнерот треба да ги содржи апликацијата и нејзините зависности за извршување, додека вредностите специфични за опкружувањето остануваат конфигурација. Зависностите за градење треба да бидат одделени од конечната слика за извршување кога е можно, а продукциските слики треба да избегнуваат алатки само за развој.
FROM php:8.3-cli AS build
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --prefer-dist --no-interaction
COPY . .
FROM php:8.3-cli
WORKDIR /app
COPY --from=build /app /app
CMD ["php", "bin/console", "app:worker"]
Овој пример е намерно нецелосен: на вистинска слика можеби ќе ѝ требаат PHP екстензии, веб-сервер или менаџер на процеси и чекор за загревање на кешот специфичен за рамката. Важниот принцип е дека градењето треба да биде повторливо од декларираните влезови. Не вградувајте акредитиви во слики и не потпирајте се на изменлив директориум на серверот како недекларирана зависност.
Перформансите почнуваат со видливост
Работата на перформансите често е погрешно насочена затоа што тимовите го оптимизираат она што изгледа бавно наместо она што е бавно. Воспоставете видливост на времетраењето на барањата, стапките на грешки, длабочината на редиците, латентноста на базата на податоци и бавните пребарувања пред да направите широки промени. Потоа следете едно барање низ неговите скапи граници: далечински повици, серијализација, пристап до датотечниот систем, промашувања во кешот и пребарувања во базата на податоци.
Кеширањето е компромис меѓу брзината и свежината, а не универзален лек. Кеширајте податоци со јасен модел на сопственост и стратегија за инвалидирање. Ако инвалидирањето е тешко, кратко време на живот може да биде побезбедно отколку да се преправате дека податоците се трајни. За скапа работа, разгледајте асинхрона обработка, но јасно соопштете го статусот наместо да ги оставите клиентите да нагаѓаат дали барањето успеало.
Направете ја промената рутинска операција
Одржливоста не е естетска уредност. Таа е способност да се направи правилна промена со сигурност. Малите, кохезивни модули помагаат, но исто така помагаат и тестовите на важните граници: тест на API договор, интеграциски тест на складиште со вистинска база на податоци и фокусирани тестови за деловните правила. Тестовите што само ги пресликуваат деталите на имплементацијата имаат тенденција да го отежнат рефакторирањето.
Оперативната подготвеност припаѓа во истиот разговор. Секој сервис треба да има корисни логови, идентификатори за корелација каде што барањата преминуваат граници, здравствени проверки што ги одразуваат неговите зависности и план за враќање назад при ризични изданија. Распоредувањето не е завршено кога кодот работи; завршено е кога тимот може да утврди дали тој се однесува правилно.
Најотпорниот backend не е оној со најмногу шаблони. Тој е оној во кој следната неопходна промена има очигледно, тестираливо и набљудливо место каде што може да живее.
PHP останува практична основа за системи што се развиваат затоа што јазикот и екосистемот им дозволуваат на тимовите да преминат од едноставна апликација барање-одговор кон редици, контејнери, типизирани сервиси и дисциплинирано распоредување без да ја напуштат платформата. Архитектурата што трае не е замрзнат дијаграм на стекот. Таа е збир од избори што ги одржува кодот, податоците и операциите подготвени за следното искрено барање.