Системска архитектура: Изградете бекенди што ги надминуваат очекувањата
Повеќето неуспеси на бекендот не се предизвикани од недостасувачка функционалност на рамката или некоја нејасна поставка на базата на податоци. Тие почнуваат порано: системот е дизајниран околу денешната идеална патека, а потоа од него се бара да го издржи утрешниот сообраќај, интеграции, промени во тимот и деловни правила.
Добрата архитектура не значи градење на најсложениот можен систем. Таа значи свесно правење компромиси, така што следната важна промена е безбедна, разбирлива и прифатлива по цена. Бекенд што ги надминува очекувањата обично е досаден на вистинските места: јасни граници, предвидливи текови на податоци, корисна набљудливост и процес на распоредување што не зависи од среќа.
Почнете со одговорностите, а не со технологиите
Изборот на PHP, Docker, PostgreSQL, Redis или брокер за пораки е важен, но тие избори треба да следат од обликот на проблемот. Почнете со идентификување на одговорностите што вашиот систем мора да ги преземе: автентикација, нарачки, наплата, известувања, известување, обработка на датотеки или синхронизација со трети страни.
Одржете ја првата граница едноставна. Модуларен монолит често е најсилната почетна точка: една апликација што се распоредува, со добро дефинирани внатрешни модули. Тој ги избегнува оперативните трошоци на дистрибуираните системи, а истовремено спречува кодната база да стане една недиференцирана маса.
На пример, модулот за нарачки не треба директно да пристапува до табелите за плаќања или да испраќа е-пораки од својот контролер. Тој треба да изложи операција на ниво на апликација како placeOrder(), да ја зачува сопствената состојба преку репозиториум или слој за пристап до податоци и да емитува доменски настан кога трансакцијата ќе успее. Известувањата и плаќањата можат да реагираат преку експлицитни интерфејси или асинхрони потрошувачи.
Оваа структура ги прави зависностите видливи. Исто така, создава практичен пат за подоцнежно издвојување ако на некој модул навистина му треба независно скалирање или циклуси на издавање.
Дизајнирајте API како трајни договори
API не е само рута што враќа JSON. Тоа е договор што го користат веб-клиенти, мобилни апликации, интеграции и идни развивачи. Неформалниот пристап кон него создава скапи проблеми со компатибилноста.
Користете крајни точки ориентирани кон ресурси таму каде што одговараат, но дајте предност на доследноста пред чистотата. Дефинирајте предвидливи обвивки за одговори, грешки при валидација, однесување при пагинација, правила за автентикација и очекувања за идемпотентност. Ако креирањето плаќање или нарачка може да се повтори, прифатете клуч за идемпотентност и зачувајте го резултатот поврзан со тој клуч. Мрежен тајмаут не треба тивко да создаде две наплати.
public function store(CreateOrderRequest $request): JsonResponse
{
$result = $this->orders->place(
$request->validated(),
$request->header('Idempotency-Key')
);
return response()->json($result, 201);
}
Контролерот останува тенок затоа што го преведува HTTP во повик кон апликацијата. Сервисот го поседува работниот тек; валидацијата и авторизацијата се експлицитни; деталите за зачувувањето остануваат надвор од транспортниот слој.
Верзионирајте само кога промена што ја нарушува компатибилноста е неизбежна. Додавањето опционално поле обично е побезбедно од заменување на обликот на одговорот. Отстранувањето или менувањето поле на кое се потпираат клиентите заслужува нова верзија, период на миграција и јасна комуникација за повлекувањето.
Направете ја базата на податоци првокласна одлука за дизајн
Перформансите на базата на податоци често се одредуваат од моделирањето на податоците и обликот на барањата, а не од итен кеш. Моделирајте ги односите што се важни, ограничете ги невалидните состојби таму каде што е практично и индексирајте според реалните шеми на пристап.
Ако административен екран постојано филтрира нарачки според сметка и време на креирање, индекс што одговара на таа шема на пристап е повреден од нејасна колекција индекси со една колона. Ако барањето стане бавно, проверете го неговиот план за извршување пред да нагаѓате. Индексот може да ги забрза читањата, но да ја зголеми цената на запишувањето и складирањето, па секој индекс треба да ја оправда својата улога.
Трансакциите се подеднакво важни. Ажурирањето залихи, евидентирањето нарачка и резервирањето состојба на плаќање можеби треба да успеат или да не успеат заедно. Одржувајте ги трансакциите тесни, избегнувајте мрежни повици во нив и избирајте однесување за изолација со свест за истовремените барања.
- Користете ограничувања на базата на податоци за инваријанти што никогаш не смеат да се прекршат.
- Чувајте ги временските ознаки доследно и конвертирајте ги за прикажување на рабовите.
- Пагинирајте големи колекции; избегнувајте вчитување неограничен резултат во PHP меморијата.
- Мерете ги бавните барања и поправете го барањето или шемата пред да го прикриете проблемот со кеширање.
Користете асинхрона работа намерно
Редиците се одлични за работа што не мора да заврши пред корисникот да добие одговор: испраќање е-пораки, генерирање извози, промена на големината на прикачувања, повикување бавни надворешни сервиси или обработка на аналитички настани. Тие не се замена за разбирање на неуспехот.
Сигурна задача мора да толерира повторни обиди. Тоа значи користење идемпотентни операции, ограничени политики за повторни обиди, корисни логови и стратегија за задачи што постојано не успеваат. Работник што испраќа е-порака со потврда треба да евидентира дека го направил тоа или да користи идентификатор на барање од добавувачот каде што е достапен, наместо слепо повторно да испраќа по неизвесен тајмаут.
За настани создадени заедно со промени во базата на податоци, разгледајте ја шемата outbox. Запишете ја деловната промена и записот за настан во истата трансакција, па потоа објавете ги настаните што чекаат преку работник. Ова ја намалува празнината во која нарачката е потврдена, но нејзиниот последователен настан е изгубен бидејќи процесот откажува веднаш потоа.
Контејнеризирајте за повторливост, а не за формалност
Docker е најкорисен кога локалниот развој, тестирањето и распоредувањето личат едни на други. Експлицитно дефинирајте го PHP извршното опкружување, екстензиите, зависностите и влезните точки на процесите. Чувајте ја конфигурацијата во променливи на околината или управувани тајни, а не вградена во слики или изворен код.
docker compose up -d
docker compose exec app php artisan migrate --force
docker compose exec app php artisan queue:work
Овие команди се безбедни само кога околината е јасна. Миграциите треба да се прегледуваат како промени во продукција, соодветно да се прават резервни копии и да се дизајнираат за компатибилност при пуштање. Распоредувањето може накратко да извршува стари и нови верзии на апликацијата истовремено, па деструктивните промени на шемата често бараат фазни изданија: додадете nullable колона, распоредете код што ги запишува двете форми ако е потребно, пополнете ги постојните податоци, префрлете ги читањата, а потоа подоцна отстранете ја старата структура.
Перформансите се својство на системот
Брзите крајни точки се резултат на многу мали одлуки. Избегнувајте N+1 барања со ефикасно вчитување познати односи. Поставете тајмаути за врските со базата на податоци и за излезните HTTP повици. Повторно користете врски таму каде што вашиот модел на извршување го поддржува тоа. Враќајте само полиња што му се потребни на клиентот. Кеширајте стабилни, скапи резултати со експлицитни правила за поништување.
Кеширањето заслужува воздржаност. Кешот е уште еден систем со состојба и со начини на откажување: застарени податоци, трки при поништување, притисок врз меморијата и лавини во кешот. Кеширајте докажано тесно грло, дефинирајте го прифатливиот прозорец на свежина и осигурете се апликацијата и понатаму правилно да се однесува при промашување на кешот или прекин.
Набљудливоста ја претвора архитектурата од теорија во оперативна практика. Собирајте структурирани логови со идентификатори на барања, следете стапки на грешки и латентност и инструментирајте важни задачи во позадина. Кога ќе се случи инцидент, целта не е да имате повеќе логови; туку брзо да одговорите што откажало, кој бил засегнат и дали системот се опоравува.
Градете за тимот што следи
Одржливоста е архитектонска функционалност. Именувајте ги модулите според деловните концепти, одржувајте ги функциите доволно мали за да може да се расудува за нив, тестирајте го однесувањето на соодветниот слој и документирајте одлуки што инаку би изгледале произволни. Краток запис за архитектонска одлука што објаснува зошто е избран синхрон повик наместо редица може да заштеди часови идни дебати.
Најсилниот бекенд не е оној со најмногу компоненти. Тој е оној чие однесување останува јасно кога барањата стануваат незгодни. Изградете експлицитни граници, заштитете го интегритетот на податоците, очекувајте повторни обиди и делумен неуспех и мерете го она што корисниците навистина го доживуваат. Така системот го заслужува правото да расте.