Разложување на монолити: прагматичен пат кон микросервиси
Монолитот не е неуспех. Често е најбрзиот начин корисна идеја да се претвори во функционален производ: едно распоредување, една база на код, една база на податоци и помалку подвижни делови што треба да се разберат. Проблемите почнуваат кога апликацијата ќе ги надрасне границите што нејзината првична структура може удобно да ги поддржи. Изданијата стануваат ризични, мала промена бара познавање на неповрзани области, а тимовите трошат повеќе време на координација отколку на испорака.
Микросервисите можат да помогнат, но не се лек за секоја проблематична база на код. Тие ја заменуваат сложеноста во процесот со сложеност на дистрибуирани системи: мрежни неуспеси, верзионирани договори, асинхрони работни текови, набљудливост и оперативен товар. Прагматичната цел не е „да се разгради монолитот“. Таа е да се подобри способноста на системот безбедно да се менува.
Започнете со пронаоѓање на точките на притисок
Не почнувајте со технолошки дијаграм. Почнете со докази. Кои делови од апликацијата се менуваат најчесто? Кои распоредувања се опасни? Каде одделни тимови се блокираат меѓусебно? Кои оптоварувања имаат потреба од независно скалирање? Тие одговори откриваат дали границата на сервисот ќе реши вистински проблем или само ќе создаде нов.
Во PHP апликација, типична точка на притисок може да биде тек на наплата што опфаќа цени од каталогот, залихи, плаќања, известувања и известување. Извлекувањето на сите тие одговорности одеднаш е покана да се создаде дистрибуиран монолит. Подобар прв кандидат обично е способност со јасна намена, ограничени зависности и тим што може да ја поседува од почеток до крај.
Добрите рани кандидати често вклучуваат:
- Генерирање и испорака на известувања.
- Обработка на конверзии на медиуми или документи.
- Индексирање за пребарување и опслужување на барања.
- Генерирање извози и друга долготрајна работа во заднина.
- Посебна интеграциска граница, како адаптер за давател на плаќања.
Овие области обично имаат природно асинхрона работа, мерливо оптоварување и помалку причини да споделуваат трансакциска состојба со остатокот од апликацијата.
Направете го монолитот модуларен пред да извлечете што било
Заплетканиот монолит не станува чист само затоа што неговите папки се преместени во посебни складишта. Прво создадете граници во постојната апликација. Групирајте го кодот според деловна способност, наместо само според технички слој. Одржувајте ги контролерите тенки, поставете ги работните текови на апликацијата зад експлицитни интерфејси и спречете модулите да навлегуваат во моделите за перзистентност еден на друг.
На пример, модул за нарачки не треба директно да ги менува табелите за залихи или да повикува поштар за известувања. Тој може да објави намера како OrderPlaced. Во монолитот, друг модул може да го обработи тој настан во истиот процес. Подоцна, тој обработувач може да се премести зад редица без да го принуди кодот за нарачување да дознае за деталите на посредникот.
final class PlaceOrder
{
public function __construct(
private OrderRepository $orders,
private EventPublisher $events,
) {
}
public function handle(PlaceOrderCommand $command): Order
{
$order = Order::place($command->customerId, $command->items);
$this->orders->save($order);
$this->events->publish(new OrderPlaced($order->id()));
return $order;
}
}
Точната рамка е помалку важна од насоката на зависностите. Деловниот код треба да зависи од стабилни апстракции, додека HTTP клиентите, редиците, ORM моделите и SDK-ата на давателите остануваат на рабовите. Ова го прави извлекувањето намерна инфраструктурна промена, наместо препишување на деловните правила.
Извлекувајте според способност, не според табела во базата на податоци
„Еден сервис по ентитет“ звучи уредно, но обично создава разговорливи API-ја и кревки работни текови. Клиентите, нарачките, производите и плаќањата се податочни концепти; тие не се автоматски граници на сервиси. Корисна граница поседува деловна способност, нејзините правила и податоците потребни за спроведување на тие правила.
Штом сервисот поседува податоци, другите сервиси не треба директно да запишуваат во неговата база на податоци. Директниот пристап до споделена база на податоци создава скриено спрегнување: миграција на шемата во еден тим може тивко да го наруши продукциското оптоварување на друг тим. Наместо тоа, изложете договор, без разлика дали станува збор за HTTP API, поток на пораки или за двете.
Тоа не значи дека секое барање мора да стане оддалечено. Практичен премин често ги остава главната апликација и новоизвлечениот сервис привремено да делат база на податоци, со јасно наведен рок и план за миграција. Третирајте го тој аранжман како скеле. Ако стане траен, сервисот не може навистина да се развива независно.
Дизајнирајте договори за промена
API-то на сервисот треба да опишува деловни дејства, а не да го открива распоредот на неговото складирање. Претпочитајте крајна точка или команда како POST /payment-authorizations наместо API што бара од повикувачите да манипулираат со редови за плаќање. Враќајте стабилни идентификатори и експлицитни состојби. Додавајте полиња на компатибилен начин, толерирајте непознати полиња каде што е соодветно и верзионирајте само кога компатибилната еволуција веќе не е можна.
Мрежните повици можат да не успеат откако оддалечениот сервис ќе ја заврши работата, но пред повикувачот да прими одговор. За операции што не смеат да се случат двапати, поддржете идемпотентност. Клиентот може да прикачи единствен клуч кон барање за плаќање; сервисот го евидентира резултатот поврзан со тој клуч и го враќа при повторен обид, наместо повторно да наплати.
Прифатете ја евентуалната конзистентност таму каде што припаѓа
Една трансакција во база на податоци е погодна затоа што сите поврзани промени или успеваат заедно или не успеваат заедно. Меѓу сервисите, таа погодност исчезнува. Обидот да се пресоздаде со дистрибуирани трансакции обично ја зголемува кревкоста и ја намалува достапноста.
Наместо тоа, идентификувајте ги инваријантите што навистина бараат непосредна конзистентност. Резервирањето залиха пред потврдување на нарачка може да биде една. Ажурирањето бројач на контролна табла обично не е. За второто, објавете настан и дозволете сервисот за известување асинхроно да го ажурира сопствениот модел за читање.
Сигурното објавување на настани бара повеќе од запишување во редица по потврдување на трансакција во база на податоци. Ако процесот не успее меѓу тие два чекора, промената во базата на податоци постои, но настанот е изгубен. Шемата outbox го решава ова: запишете ја деловната промена и запис за настан во истата локална трансакција, а потоа нека работник испорачува записи што чекаат. Потрошувачите сепак мора да бидат идемпотентни, бидејќи испораката може да се случи повеќе од еднаш.
Градете оперативна дисциплина паралелно со сервисите
Секој нов сервис додава одговорности за распоредување, конфигурација, евидентирање, здравствени проверки, метрики и предупредувања. Docker може да го направи локалниот развој повторлив, но контејнерот не е оперативен модел. На тимовите им е потребен јасен начин да одговорат: кое барање не успеа, која зависност го предизвика тоа и дали повторниот обид е безбеден?
Користете структурирани логови со идентификатори на барање или корелација. Мерете латентност, стапки на грешки, длабочина на редица и неуспеси на зависности. Поставете временски ограничувања за излезни повици; отсутното временско ограничување може да ги исцрпи PHP работниците додека чекаат нездрава зависност. Повторувајте само привремени неуспеси, користете ограничено постепено одложување и избегнувајте повторување неидемпотентни дејства, освен ако сервисот што прима изрично поддржува безбедно отстранување дупликати.
Распоредувајте постепено. Насочете мал, повратен дел од сообраќајот низ новата патека, споредете ги исходите и задржете патека за враќање додека расте довербата. Ознаките за функции и тестовите на договори се вредни тука: првите ја контролираат изложеноста, додека вторите потврдуваат дека потрошувачите и давателите сè уште се согласуваат за однесувањето.
Нека архитектурата биде заслужена
Најуспешното патување кон микросервиси обично е негламурозно. Почнува со модуларен монолит, извлекува една граница што заслужила независност и учи од резултирачкиот оперативен трошок. Некои домени ќе останат заедно бидејќи нивните правила и трансакции припаѓаат заедно. Тоа е здраво инженерство, не нецелосна трансформација.
Архитектурата треба да ја направи следната важна промена полесна од претходната. Ако помал, подобро структуриран монолит го постигнува тоа, задржете го. Ако независно поседуван сервис навистина го намалува притисокот од координација, ризикот при распоредување или притисокот за скалирање, извлечете го внимателно. Целта не е дијаграм исполнет со кутии; таа е систем што останува разбирлив, сигурен и подготвен да се развива.