Системска архитектура: Планирајте за променливите потреби, не само за денешните барања
Повеќето системи не пропаѓаат затоа што првата верзија била премногу едноставна. Пропаѓаат затоа што првата верзија тивко станала трајна обврска.
Функционалноста пристигнува со рок, се додава табела, се изложува endpoint и Docker контејнерот успешно се стартува. Работата може да биде сосема разумна за денес. Проблемот почнува кога првичните претпоставки никогаш не стануваат видливи: еден тип клиент, еден тек на плаќање, една цел за распоредување, една потреба за известување. Месеци подоцна, секое ново барање мора да се протне низ избори што требало да бидат привремени.
Добрата системска архитектура не е обид да се предвиди секоја иднина. Таа е дисциплина на правење на веројатните промени достапни, додека денешното решение останува разбирливо.
Дизајнирајте според промените, а не според замислениот обем
„Отпорно на иднината“ е некорисна цел. Ниту еден дизајн не е отпорен на непознати барања, а градењето за секоја можност создава апстракции што никој не може безбедно да ги менува. Подобро прашање е: кои делови од овој систем најверојатно ќе се променат и колку би чинела нивната промена?
За PHP backend, веројатните точки на промена често вклучуваат деловни правила, надворешни интеграции, авторизација, сопственост на податоци и известување. Контролер што ги меша сите овие грижи може совршено да работи на почетокот, но секое подоцнежно прилагодување го прави ризично. Целта не е секоја класа да се подели на десетина слоеви. Целта е нестабилните одлуки да имаат јасно место.
На пример, HTTP-грижите задржете ги во контролерите, текот на апликацијата во наменски сервиси или акции, а перзистенцијата зад модели или репозиториуми само кога репозиториумот навистина додава корисна граница. Тенка обвивка околу ORM пребарување не е архитектура. Граница што спречува деталите за давателот на плаќања да протекуваат низ целиот код може да биде.
Почнете со модуларен монолит
За повеќето производи, модуларниот монолит е најсилната почетна опција. Тој ги одржува распоредувањето, дебагирањето, трансакциите и локалниот развој едноставни, додека му овозможува на кодот да развие значајни внатрешни граници.
Модулите треба да одразуваат деловни способности, а не само технички категории. Структура на директориуми организирана само како Controllers, Services и Models може да се претвори во лов на богатство како што расте апликацијата. Групирањето на кодот околу области како нарачки, наплата, залихи и идентитет ги прави сопственоста и зависностите полесни за воочување.
Важниот тест е насоката на зависностите. Наплатата може да праша компонента за идентитет дали на некоја сметка ѝ е дозволено да плати, но идентитетот не треба да ги знае правилата за фактури на наплатата. Кога модулите слободно зависат еден од друг, секоја функционалност станува промена на ниво на цел систем.
Користете интерфејси таму каде што зависноста е реална
Интерфејсите се корисни кога апликацијата зависи од способност што може да има повеќе имплементации или ѝ е потребна изолација од надворешен систем. Текот за известување може да зависи од испраќач без да знае дали испораката се врши преку е-пошта, редица или API од трета страна.
interface InvoiceSender
{
public function send(Invoice $invoice): void;
}
final class SendInvoice
{
public function __construct(private InvoiceSender $sender)
{
}
public function handle(Invoice $invoice): void
{
$this->sender->send($invoice);
$invoice->markAsSent();
}
}
Оваа граница е вредна затоа што политиката за испорака може да се менува независно од текот на фактурирање. Би била помалку вредна ако интерфејсот постоеше само за да скрие еден стабилен внатрешен помошник. Апстракцијата треба да ја намали спрегнатоста, а не само да го зголеми бројот на датотеки.
Нека базата на податоци ги спроведува вистините што ѝ припаѓаат
Валидацијата во апликацијата ги подобрува повратните информации за корисникот, но не е замена за интегритетот на базата на податоци. Барањата може повторно да се испраќаат, задачите може да работат истовремено, скриптите може да заобиколат формулар, а со време може да се воведе и втор коден пат. Базата на податоци е конечниот заеднички авторитет над зачуваните податоци.
Користете странски клучеви таму каде што се потребни односи, уникатни ограничувања за вредности што мора да бидат уникатни и соодветни индекси за пребарувањата што апликацијата навистина ги извршува. Ако бројот на нарачката не смее да се повтори, спроведете го тоа правило во шемата, како и во PHP. Ако запис-дете не може да постои без својот родител, моделирајте го тој однос експлицитно.
Еволуцијата на шемата заслужува иста грижа како и кодот на апликацијата. Безбедна миграција често следи образец на проширување и стеснување:
- Додадете нова nullable колона, табела или индекс без да ја нарушите апликацијата што работи.
- Распоредете код што може да ја чита старата форма и да ја запишува новата форма.
- По потреба, пополнете ги постојните податоци во контролирани серии.
- Префрлете ги читањата на новата репрезентација откако ќе ги проверите податоците.
- Отстранете ја старата патека само откако веќе не се користи.
Ова е помалку драматично од една деструктивна миграција, но на распоредувањата им дава пат за опоравување. Тоа е важно и кога повеќе инстанци на апликацијата накратко извршуваат различни верзии за време на rollout.
Направете ги API-јата експлицитни за договорите и неуспехот
API е договор, а не само рута што враќа JSON. На клиентите им се потребни стабилни значења на полињата, предвидливи грешки при валидација, однесување на авторизацијата, правила за пагинација и јасен одговор кога работата не може да се заврши.
Верзионирањето е една опција, но компатибилноста често почнува со поедноставни навики: додавајте полиња наместо да ги преименувате или отстранувате, избегнувајте промена на типот на поле и третирајте ги payload-ите за грешки како дел од договорот. Ако јавен endpoint прифаќа барање што создава ресурс, повторните обиди заслужуваат посебно внимание. Мрежен timeout може да го остави клиентот несигурен дали серверот ја завршил работата.
За операции што не смеат да создадат дупликати, клуч за идемпотентност може да му овозможи на серверот да ги поврзе повторените барања со првичниот резултат. Тој клуч мора да се зачува и доследно да се обработува; само прифаќањето на header без евидентирање на неговиот исход не ја прави операцијата идемпотентна.
На надворешните API-ја им е потребен слично дефанзивен третман. Поставете timeouts, разликувајте неуспеси што може повторно да се обидат од трајните и избегнувајте слепо повторување на неидемпотентни барања. Ставете ја асинхроната работа во редица кога на корисникот не му е потребен моментален резултат, но дизајнирајте ги задачите да толерираат дупликатна испорака. Обработката барем еднаш е честа; деловната акција мора да биде безбедна ако повторно се изврши.
Контејнерите треба да ги направат операциите здодевни
Docker помага кога ги прави околините повторливи, а не кога крие неизвесност. Изградете една слика на апликацијата, обезбедете конфигурација преку променливи на околината или управуван механизам за тајни и држете ги услугите со состојба, како базите на податоци, одделно од контејнерот на апликацијата.
Продукциската слика треба да инсталира само што ѝ е потребно за извршување. Во типична PHP апликација, зависностите може да се инсталираат при изградбата, додека запишливите патеки за време на извршувањето намерно се идентификуваат и се монтираат или обезбедуваат според потребата. Избегнувајте да претпоставувате дека локалниот датотечен систем на контејнерот е траен меѓу распоредувањата.
Проверките на здравјето треба да одразуваат корисна подготвеност. Процес што е стартуван не е нужно подготвен да опслужува сообраќај; можеби сè уште нема поврзување со базата на податоци или потребна конфигурација. Во исто време, endpoint за здравје не треба да извршува скапа работа при секоја проверка.
Перформансите се повратна спрега
Архитектурата не може да се одвои од перформансите, но предвремената оптимизација и понатаму е скапа. Измерете ја бавната патека пред повторно да ја дизајнирате. Проверете ги пребарувањата во базата на податоци, барајте повторени пребарувања во јамки, потврдете ги индексите со алатките на базата на податоци за планирање пребарувања и набљудувајте ја латентноста на редицата и стапките на грешки.
Кеширањето е најефикасно кога неговите правила за сопственост и поништување се јасни. Кеширајте изведена вредност затоа што е скапа и безбедно повторно употреблива, а не затоа што кеширањето изгледа како општ лек. Застарена цена, дозвола или број на залихи може да биде полош од бавен одговор.
Кога ќе се потврди тесно грло, изберете ја најмалата интервенција што го решава: подобро пребарување, индекс, пагинација, задача во заднина или ограничен кеш. Секое решение воведува сопствена оперативна одговорност.
Архитектурата е низа од обратливи одлуки
Најздравите системи не се оние со најмногу дијаграми или најмногу сервиси. Тоа се оние во кои развивачите можат да објаснат каде припаѓа некое правило, како безбедно се менуваат податоците, што се случува кога зависност ќе откаже и како распоредувањето може да се врати назад или да се заврши.
Изградете го најмалиот кохерентен систем за денес, а потоа заштитете ги неговите споеви. Именувајте ги границите, спроведете ги важните инваријанти, мерете пред да оптимизирате и оставете простор за следната веродостојна промена. Така архитектурата останува предност наместо да стане најскапиот дел од кодната база.