Системски дизајн: Архитектура за непознатите предизвици на утрешнината
Најтешкиот дел од дизајнот на системи не е изборот на база на податоци, цртањето дијаграм или додавањето редица. Тоа е носењето одлуки денес без да се преправаш дека можеш да го видиш секое барање што ќе дојде утре.
Добрата архитектура не ги елиминира промените. Таа им дава на промените безбедно место каде да се сместат. Таа разлика е важна бидејќи бекенд-системите ретко откажуваат поради недостиг од досетливост; тие откажуваат кога вообичаената еволуција станува скапа, ризична и тешка за разбирање.
Дизајнирајте за промени, не за замислен обем
„Отпорно на иднината“ често е скратен израз за градење многу повеќе механизми отколку што му се потребни на тековниот производ. Едноставна апликација добива магистрали за настани, повеќе сервиси, дистрибуирано следење и неколку складишта на податоци пред да има доволно сообраќај или организациска сложеност за да оправда некое од нив.
Покорисната цел е опционалност: зачувајте ја можноста да направите веројатна идна промена без да се обврзете на секоја можна идна архитектура. Почнете со наједноставниот дизајн што јасно ги исполнува денешните потреби, а потоа поставете граници околу областите што најверојатно ќе се развиваат.
За PHP апликација, тоа често значи модуларен монолит наместо непосредни микросервиси. Една единица за распоредување сепак може да има јасни модули за наплата, идентитет, каталог, известувања и известување. Секој модул ги поседува своите деловни правила и изложува намерно дефиниран интерфејс кон остатокот од апликацијата.
Ова не е компромисна архитектура. Тоа е начин трансакциите, распоредувањата, дебагирањето и локалниот развој да останат едноставни, додека се остава простор подоцна да се извлече модул ако неговите потреби за скалирање, сопственост или издавање навистина се разграничат.
Изберете граници што го одразуваат бизнисот
Техничките слоеви се корисни, но не се доволни. Проект организиран само во контролери, сервиси, репозиториуми и модели може да стане тежок за промена бидејќи неповрзаните деловни концепти завршуваат поврзани преку заеднички класи и табели.
Наместо тоа, започнете со прашањето каде се разликуваат деловните правила. Нарачката не е само ред во база на податоци. Таа има правила за цени, исполнување, откажување, плаќање и комуникација со клиентот. Тие правила заслужуваат кохезивен дом.
Практичната граница на модулот треба да разјасни неколку работи:
- Кои податоци и инваријанти ги поседува модулот.
- Кои операции може да ги побараат другите модули.
- Кои настани или резултати ги објавува по значајни промени.
- Кои детали остануваат приватни избори за имплементација.
На пример, модулот за наплата не треба директно да ги ажурира табелите за залихи само затоа што ја познава нивната шема. Треба да побара од модулот за залихи да резервира стока. На почетокот ова може да изгледа малку поформално, но спречува мала кратенка да стане трајна зависност.
Одржувајте ги интерфејсите потесни од имплементациите
На потрошувачите обично им треба помалку отколку што давателите сакаат да изложат. На API-крајна точка можеби ѝ требаат само јавното име, цената и достапноста на производот, а не целиот модел за перзистенција со белешки за добавувачи и полиња за внатрешниот работен тек.
Враќањето намерно обликувани одговори го намалува случајното поврзување и ги прави промените во шемата побезбедни. Истиот принцип важи и во кодната база: зависете од мал договор наместо од конкретна класа со десетици методи.
interface InventoryReservations
{
public function reserve(string $sku, int $quantity): ReservationResult;
}
Вредноста не е во самиот интерфејс. Вредноста е во тоа што наплатата зависи од способноста да резервира залихи, наместо од стратегијата за база на податоци што залихите случајно ја користат денес.
Нека базата на податоци го спроведува она што е важно
Кодот на апликацијата е местото каде што живее деловното однесување, но базата на податоци останува конечниот авторитет за складираните податоци. Важните инваријанти не треба да зависат само од валидатор на барања или гранка во контролер што може да се заобиколи со задача, скрипта за увоз или идна крајна точка.
Користете соодветни ограничувања: примарни клучеви, странски клучеви каде што сопственоста е стабилна, уникатни индекси, колони што не дозволуваат null и трансакции на база на податоци за промени што мора заедно да успеат или да не успеат. Ако адресата на е-пошта мора да биде единствена, направете ја единствена во базата на податоци. Ако запис за плаќање мора да се поврзува со нарачка, моделирајте ја таа врска експлицитно.
Во исто време, избегнувајте секое барање да го претворите во вежба за апстракција. Репозиториум што само обвиткува еднолиниски ORM повик додава малку заштита. Воведете побогата граница за пристап до податоци кога барањата, правилата за перзистенција, кеширањето или однесувањето на трансакциите стануваат доволно значајни за да заслужат именуван дом.
Градете API-а како долготрајни договори
API е ветување дадено на клиенти што можеби не ги контролирате. Тоа вклучува мобилни апликации, интеграции со партнери, закажани скрипти и вашиот сопствен фронтенд распореден според различен распоред.
Направете го тоа ветување едноставно и експлицитно. Валидирајте го влезот на границата, враќајте предвидливи формати на грешки, дефинирајте го однесувањето на пагинацијата и избегнувајте да изложувате одговори обликувани според базата на податоци како јавен договор. Кога промената е неизбежна, претпочитајте дополнителни промени: воведете ново опционално поле пред да отстраните старо.
Идемпотентноста заслужува особено внимание за операции што создаваат движење на пари, резервации или надворешни несакани ефекти. Мрежите повторуваат. Клиентите повторуваат. Редиците повторно испорачуваат пораки. Барање што е безбедно еднаш мора да биде дизајнирано да биде безбедно и кога ќе се прими двапати.
$key = $request->header('Idempotency-Key');
if ($existing = $payments->findByIdempotencyKey($key)) {
return $existing->response();
}
return $database->transaction(function () use ($key, $request) {
return $payments->createOnce($key, $request->validated());
});
Точната имплементација ќе варира, но суштинскиот детаљ е трајна состојба заштитена со уникатно ограничување или еквивалентен механизам безбеден за конкурентност. Проверка во меморија не е доволна кога барањата можат да стигнат до повеќе инстанци на апликацијата.
Користете асинхрона работа намерно
Редиците се одлични за работа што не мора да заврши пред корисникот да добие одговор: испраќање е-пошта, генерирање извози, обработка слики и известување надворешни системи. Тие не се лек за нејасна сопственост или ненадежна логика.
На секоја задача во позадина ѝ е потребна приказна за неуспехот. Дефинирајте го однесувањето при повторување, осигурете дека повтореното извршување е безбедно, забележете доволно контекст за дијагноза и одлучете што се случува откако повторувањата ќе се исцрпат. Задача што тивко исчезнува е полоша од синхрон неуспех бидејќи создава лажна сигурност.
За настани што следат по ажурирање на база на податоци, земете го предвид јазот меѓу потврдувањето на податоците и објавувањето порака. Ако процесот запре откако едното ќе успее, но пред другото, системите може да се разминат. Шемата на трансакциско појдовно сандаче може да помогне: запишете го појдовниот настан во истата трансакција, потоа објавете го одделно и означете го како испорачан само по успех.
Направете ги операциите дел од дизајнот
Docker може да ги направи локалните околини и распоредувањата порепродуктивни, но контејнерот не е автоматски оперативен план. Конфигурацијата треба да доаѓа од околината, тајните треба да останат надвор од сликите и репозиториумите, а дневниците на апликацијата треба да бидат доволно структурирани за да поврзат барање, задача и грешка.
Проверките на здравјето треба да одговараат на корисни прашања. Тоа што процесот е живо е различно од тоа апликацијата да е подготвена да опслужува сообраќај. Исто така, работата на перформансите треба да започне со набљудување: измерете ги бавните крајни точки, прегледајте ги барањата кон базата на податоци, идентификувајте го спорот, па оптимизирајте го вистинското тесно грло.
Предвременото кеширање е особено опасно бидејќи создава правила за поништување пред да има доказ дека е потребно кеширање. Прво додајте ги вистинските индекси, елиминирајте ги непотребните барања, пагинирајте големи колекции и поставете разумни временски ограничувања. Кеширајте само кога измерена шема на пристап го поддржува тоа.
Оставете трага за следната промена
Одржливоста не се постигнува со пишување повеќе документација отколку код. Таа произлегува од тоа важните одлуки да бидат откриливи: јасни имиња, фокусирани модули, мали тестови околу деловните правила, историја на миграции и концизни записи за архитектонските избори.
Системот што го дизајнирате не е споменик. Тој е работна средина за идните развивачи, вклучувајќи ја и верзијата од вас што повеќе не се сеќава зошто е направен одреден компромис. Архитектирајте за таа личност. Одржувајте ги границите искрени, направете ги режимите на неуспех експлицитни и дајте предност на дизајни што можат да станат пософистицирани само кога реалноста ќе го побара тоа.