Прагматична архитектура: Градење софтвер што ја надживува возбудата
Повеќето софтвери не пропаѓаат затоа што нивниот тим избрал погрешен framework. Пропаѓаат потивко: мала кратенка станува конвенција, конвенцијата станува претпоставка, а претпоставката станува скапа за промена штом пристигнат вистински корисници, вистински податоци и вистински рокови.
Прагматичната архитектура е дисциплина на донесување денешни одлуки без да се одзема премногу од утрешнината. Таа не е аргумент за сложени апстракции ниту за минимализам од типот „само пушти го“. Таа е начин системот да остане разбирлив, приспособлив и сигурен додека неговите барања сè уште се менуваат.
Почнете од обликот на проблемот
Архитектурата треба да ги следи притисоците со кои системот навистина се соочува. Backend што прифаќа плаќања има поинакви начини на откажување од интерна алатка за известување. API што опслужува мобилна апликација има потреба од стабилни договори. Работен тек со многу податоци можеби ќе има потреба од внимателно индексирање долго пред да му требаат повеќе application servers.
Корисните почетни прашања се конкретни:
- Кои податоци се доволно важни за да се заштитат, ревидираат или обноват?
- Кои барања мора да бидат брзи, а кои можат да се случуваат асинхроно?
- Што се случува кога зависност е бавна, недостапна или го враќа истиот настан двапати?
- Кои делови веројатно ќе се променат затоа што бизнисот сè уште учи?
- Како развивач ќе дијагностицира дефект откако ќе стигне во продукција?
Овие прашања водат до подобри граници од модерен архитектонски дијаграм. Скромна PHP апликација со јасен доменски слој, релациска база на податоци, queue worker и добра набљудливост може да биде многу поотпорна од збир на услуги чии сопствеништва и патеки на откажување се нејасни.
Изберете едноставни граници пред паметни апстракции
За многу backend системи, добро структуриран монолит е прагматичната почетна точка. „Монолит“ не треба да значи еден огромен слој на контролери поврзан директно со секоја табела. Тоа значи една апликација што се распоредува како целина, со намерни внатрешни граници: HTTP обработка, работни текови на апликацијата, доменски правила, перзистенција и интеграции.
На пример, работниот тек за нарачка може да изложи мала application service што ги координира валидацијата, резервацијата на залиха, перзистенцијата и настан за понатамошна работа. Контролерот го преведува HTTP влезот во тој работен тек; не треба да стане место каде што се акумулираат деловните правила.
final class PlaceOrder
{
public function __construct(
private OrderRepository $orders,
private Inventory $inventory,
private TransactionManager $transactions,
) {}
public function handle(PlaceOrderRequest $request): Order
{
return $this->transactions->run(function () use ($request): Order {
$this->inventory->reserve($request->items);
$order = Order::fromRequest($request);
$this->orders->save($order);
return $order;
});
}
}
Точните имиња на класите не се важни. Важното е што границата на трансакцијата е видлива и работниот тек може да се тестира без HTTP барање. Надворешните несакани ефекти, како испорака на е-пошта или webhook, обично припаѓаат надвор од таа трансакција. Во спротивно, бавен мрежен повик може да ги задржи заклучувањата на базата на податоци додека извршува работа што безбедно може да се повтори подоцна.
Поделувајте услуги поради причина, не по расположение
Услугите ја оправдуваат својата оперативна цена кога им треба независно распоредување, независно скалирање, поинаква безбедносна граница или навистина одделна сопственост. Дотогаш, модуларен монолит често овозможува побрза испорака и поедноставно дебагирање. Повик на метод е полесен за следење од мрежно барање, а за една трансакција во база на податоци е полесно да се размислува отколку за дистрибуирана координација.
Кога границата ќе стане реална, извлечете ја со договор, мониторинг и план за миграција. Не мешајте преместување код во друго складиште со создавање сигурна услуга.
Дизајнирајте API како долгорочни ветувања
API не е само рута што враќа JSON. Тоа е договор од кој клиентите може да зависат подолго од очекуваното. Стабилното именување, предвидливите одговори за грешки, пагинацијата и експлицитните политики за верзионирање ги намалуваат случајните кршења.
Неуспесите на валидацијата, неуспесите на авторизацијата, ресурсите што недостигаат и неочекуваните серверски грешки треба да останат разликувачки. На клиентите им треба доволно информации за да дејствуваат, но не треба да добиваат stack traces, внатрешни SQL пораки или детали за имплементацијата.
За операции за запишување што може да се повторат, особено од клиенти или webhooks, идемпотентноста е важна. Повратен повик за плаќање испорачан двапати не смее да создаде две платени нарачки. Практичен шаблон е да се зачува единствен идентификатор на надворешен настан и дупликатното внесување да се третира како веќе обработен настан, наместо да се обидувате да заклучите дупликати од временски ознаки или сличност на payload-от.
Промената што е компатибилна наназад обично е поевтина од присилната синхронизација. Додавање изборно поле во одговор често е безбедно; менување на значењето или типот на поле не е. Застарувањето е производен и комуникациски процес исто толку колку што е технички.
Дозволете базата на податоци да го носи својот дел од исправноста
Кодот на апликацијата е вреден, но базите на податоци се одлични во спроведување факти што мора да останат точни при конкурентност. Користете надворешни клучеви каде што односите се реални, единствени ограничувања каде што дуплирањето е невалидно и трансакции каде што неколку промени мора заедно да успеат или да не успеат.
Класична грешка е да се проверува достапноста во кодот на апликацијата, а потоа да се внесува подоцна без ограничување или соодветна стратегија за заклучување. При конкурентни барања, и двајцата повикувачи може да ја забележат истата достапна состојба. Базата на податоци мора да учествува во заштитата на инваријантата.
Работата на перформансите го следи истиот принцип: измерете ја вистинската патека на барањето. Индексите треба да поддржуваат познати обрасци на пристап, а не да се додаваат суеверно. Испитајте ги бавните барања, проверете ги плановите за извршување и осигурете се дека пагинацијата има експлицитно подредување. Offset пагинацијата може да стане скапа кај големи, често менливи множества податоци; пагинацијата заснована на курсор често подобро одговара кога клиентите поминуваат низ стабилен редослед на сортирање.
Направете испораката да може да се повтори
Docker е корисен кога ги прави локалниот развој и распоредувањето поконзистентни, а не кога ја крие апликацијата зад слоеви на случајна сложеност. Контејнерот треба јасно да декларира што му е потребно: runtime зависности, конфигурација доставена преку околината, предвидлива команда за стартување и локација за запишување само каде што е неопходно.
Подготвеноста за продукција вклучува и дисциплина за миграции. Извршувајте миграции на шемата како експлицитен чекор при распоредувањето, разберете дали секоја миграција е безбедна со постоечкиот сообраќај и избегнувајте да поврзувате издание на код со промена на шема што не може да коегзистира со претходната верзија. Промените „прошири и стесни“ често се побезбедни: додајте нова колона или табела, распоредете код што ги поддржува двете форми, мигрирајте ги податоците, па подоцна отстранете ја старата патека.
Повторните обиди заслужуваат еднакво внимание. Повторувањето на привремен мрежен неуспех може да помогне; повторувањето на секој исклучок може да засили прекин или да дуплира несакани ефекти. Користете ограничени повторни обиди, backoff, временски ограничувања и идемпотентни обработувачи. Испраќајте ја трајно неуспешната работа во позадина некаде видливо за да може да се истражи, наместо тивко да се отфрли.
Оптимизирајте за следната личност
Одржливоста не е нејасна претпочитаност за уреден код. Таа е брзината со која некој може безбедно да одговори: на што влијае оваа промена, како да го тестирам и што ќе се случи ако не успее?
Тоа значи да се чува конфигурацијата одделно од кодот, да се евидентира значаен контекст без да протекуваат тајни, да се изложуваат проверки на здравјето што одразуваат корисни зависности и да се пишуваат тестови околу важното однесување, наместо околу приватни детали за имплементацијата. Тоа значи и да се бришат апстракции што повеќе не разјаснуваат ништо.
Најдобрата архитектура ретко е најимпресивниот дијаграм. Таа е онаа што му овозможува на тимот да направи промена со самодоверба, да ги набљудува нејзините последици, да се опорави кога реалноста не се согласува и да продолжи да учи без повторно градење на целиот систем. Хајпот брзо избледува. Јасните граници, искрените компромиси и системите со кои лесно се управува имаат многу подолг полуживот.