Системска архитектура: Зошто е важен едноставен, одржлив код
Повеќето системи не пропаѓаат затоа што на тимот му недостасувала досетливост. Тие пропаѓаат затоа што обичните промени станале ризични: ново API поле бара измени на пет места, миграцијата на база на податоци има нејасна одговорност, Docker слика се однесува различно во продукција или „привремената“ апстракција сега е невозможно да се отстрани.
Противотровот ретко е модерна архитектура. Тоа е здодевен код што лесно се одржува: код со очигледни граници, познати алатки, предвидливи начини на откажување и трошок што останува разбирлив откако ќе се заборави првичната имплементација.
Здодевното е дизајнерски избор, а не недостиг на амбиција
„Здодевно“ не значи невнимателно, старомодно или отпорно на подобрување. Тоа значи да се избере наједноставниот дизајн што ја задоволува тековната оперативна потреба и може безбедно да се менува од компетентен развивач кој не бил присутен кога бил дизајниран.
Конвенционална PHP апликација со јасен тек на барањето, релациска база на податоци, експлицитни миграции, позадински работници каде што се потребни и мал број добро дефинирани интеграции често е посилен систем од збир на сервиси поврзани со редици, генерирани клиенти и сопствена инфраструктура.
Комплексноста има навика да пристигне пред нејзините придобивки. Секој дополнителен сервис додава грижи околу распоредување, логирање, конфигурација, автентикација, мониторинг, верзионирање и одговор на инциденти. Тие трошоци може да се оправдани, но треба да се платат за реален проблем, а не за замислена иднина.
Оптимизирајте за следната безбедна промена
За архитектурата често се зборува како за дијаграм. Во пракса, нејзиниот квалитет се покажува кога некој треба да направи промена во напорна недела. Може ли да го најде релевантниот код? Може ли да го тестира локално? Може ли да утврди кои податоци ќе се променат? Може ли да го распореди без да координира десетина неповрзани компоненти?
Систем што лесно се одржува го прави својот најчест пат експлицитен. За типична backend крајна точка, тоа може да биде:
- контролер што го валидира и преведува HTTP барањето;
- апликациски сервис што го извршува случајот на употреба;
- слој на репозиториум или прашања што ги поседува деталите за перзистенција;
- правила на ниво на домен изразени блиску до податоците што ги штитат; и
- мапер за одговори што ја прави API репрезентацијата намерна.
Овие граници не се барање за формалност. На мала крајна точка можеби ѝ требаат само контролер и сервис. Поентата е да се избегне криење на деловните правила во код за шаблони, објекти за барања, ORM повратни повици или расфрлани помошни функции. Кога правилото има име и место, може да се тестира и менува.
Направете ги зависностите видливи
Скриените зависности создаваат изненадувачко однесување. Метод што тивко го чита тековниот корисник, отвора трансакција, испраќа е-пошта и поништува кеш можеби е погоден на почетокот, но подоцна е тешко да се расудува за него.
Претпочитајте експлицитни влезови и експлицитни исходи. Сервис со име approveInvoice() треба јасно да покаже дали запишува податоци, емитува настан или може да го одбие барањето. Исклучоците се корисни за исклучителни неуспеси, но очекуваните деловни исходи често се појасни кога се претставени директно во текот на апликацијата.
final class ApproveInvoice
{
public function __construct(
private InvoiceRepository $invoices,
private TransactionManager $transactions,
) {}
public function handle(int $invoiceId, int $approverId): void
{
$this->transactions->run(function () use ($invoiceId, $approverId): void {
$invoice = $this->invoices->getForUpdate($invoiceId);
$invoice->approve($approverId);
$this->invoices->save($invoice);
});
}
}
Точните интерфејси ќе се разликуваат, но важен е видливиот начин на однесување. Читателот може да види дека оваа операција ја менува трајната состојба и бара трансакциска обработка.
Нека базата на податоци ја штити вистината
Валидацијата во апликацијата ја подобрува повратната информација за корисникот; ограничувањата во базата на податоци го штитат системот. И двете се важни. Ако адресата на е-пошта мора да биде единствена, или подреден ред не може да постои без родителски ред, кодирајте го тоа правило во шемата, како и во кодот на апликацијата.
Ова е особено важно при конкурентност. Две барања можат да ја поминат проверката на ниво на апликација „дали ова постои?“ пред кое било да запише. Единствениот индекс е конечниот авторитет. Апликацијата потоа треба чисто да се справи со настанатиот конфликт, наместо да претпоставува дека тајмингот секогаш ќе биде наклонет.
Промените во базата на податоци ја заслужуваат истата дисциплина како и промените во кодот. Користете верзионирани миграции, направете ги деструктивните операции намерни и земете го предвид редоследот на распоредување. Додавањето nullable колона обично е полесно за воведување отколку веднаш да се бара нова non-null вредност од секоја стара инстанца на апликацијата. Пополнувањето на постојните податоци и промените на ограничувањата може да бидат одделни чекори кога тоа ги прави враќањето назад и проверката побезбедни.
Одржувајте ги API-јата намерно едноставни
API е договор, а не само рута што враќа JSON. Стабилното именување, предвидливите облици на грешки, правилата за пагинација и јасното однесување при автентикација го намалуваат триењето за секој повикувач.
Не ги изложувајте табелите од базата на податоци директно во API одговорите само затоа што ORM го прави тоа лесно. Обликот на одговорот треба да го одразува она што им е потребно на потрошувачите, а не секоја колона што случајно постои. Ова исто така му дава простор на backend-от да ја реорганизира внатрешната меморија без да ги принудува клиентите да се менуваат.
За промени, адитивната еволуција обично е попријателска од замената. Воведете ново опционално поле, поддржете го кај сите повикувачи, а потоа повлечете го старото поле преку најавен и измерен процес. Верзионирањето може да биде корисно, но не ја елиминира потребата внимателно да се управува со компатибилноста.
Користете Docker за да ги намалите разликите, а не да додавате слоеви
Контејнерите се вредни кога го прават локалниот развој, тестирањето и распоредувањето поповторливи. Стануваат штетни кога Docker поставувањето е позагадочно од апликацијата што ја извршува.
Добра изградба на контејнер е читлива: инсталирајте зависности детерминистички, копирајте само што е потребно, обезбедете конфигурација преку околината или механизам за управувани тајни и извршувајте ја истата апликациска команда што ја очекувате во продукција. Каде што е практично, држете ги погодностите само за развој одделно од продукциската слика.
Не мешајте успешно градење на контејнер со здраво распоредување. Апликацијата сè уште мора да стартува со својата вистинска конфигурација, да ги достигне своите зависности и да изложи значајни проверки на здравјето. Проверката на здравјето треба да укажува дека процесот може да ја извршува својата наменета улога, а не само дека некоја порта е отворена.
Перформансите почнуваат со мерење и едноставност
Работата на перформансите е едно од најлесните места за создавање трајна комплексност како одговор на привремен симптом. Пред да воведете кеш, редица, реплика или ново складиште на податоци, утврдете го вистинското тесно грло. Дали бавната патека е прашање без индекс, случајно N+1 прашање, повторени далечински повици, преголема големина на товарот или заситен работник?
Почнете со најмалку инвазивната поправка. Подобрете го прашањето и проверете го неговиот план. Намерно преземете ги поврзаните податоци. Додадете пагинација. Поставете временски ограничувања за појдовните повици. Додадете ограничено однесување за повторен обид околу неуспеси што се навистина привремени и направете ги запишувањата што може повторно да се обидат безбедни преку идемпотентност или трајна стратегија за дедупликација.
Кешовите се корисни, но додаваат уште една вистина што треба да се синхронизира. Третирајте ги поништувањето, истекувањето, застарените читања и испадите на кешот како нормални дизајнерски прашања пред да го третирате кеширањето како стандардна оптимизација.
Направете го оперативното однесување дел од архитектурата
Системот лесно се одржува само ако може да се разбере додека работи. Структурираните логови треба да вклучуваат доволно контекст за да поврзат барање, задача или неуспех со релевантната операција без да изложуваат чувствителни податоци. Метриките треба да одговараат на оперативни прашања: дали барањата не успеваат, дали задачите се натрупуваат и дали се менува латентноста? Алармите треба да бидат поврзани со услови што бараат дејство.
Подеднакво важно е да се документираат малите нешта што инаку живеат во меморијата: како да се извршуваат миграции, како безбедно да се репродуцира неуспешна задача, што менува враќањето назад и која зависност поседува конфигурациска вредност. Документацијата не мора да биде долга; мора да одговара на реалноста.
Трајната предност
Кодот што лесно се одржува не е помалку способен од досетливиот код. Тоа е код што ја задржува досетливоста за местата каде што ја оправдува својата цена. Тој им овозможува на тимовите да ја трошат својата енергија на деловното правило, проблемот на клиентот или ризикот за доверливост што навистина го разликува производот.
Најдоброто архитектонско прашање често не е „Што би било највпечатливо?“ Туку „Што ќе ја направи следната неопходна промена безбедна, јасна и повратна?“ Изберете го одговорот што го одржува системот разбирлив. За една година, тој очигледен недостиг на драма може да биде највредната инженерска одлука што сте ја донеле.