ИТ развој

Decouple Your PHP Services: Forge Robust APIs, Not Brittle Dependencies

Раздвојте ги вашите PHP услуги: создавајте робусни API-ја, а не кршливи зависности

PHP услугата ретко станува тешка затоа што една класа има премногу методи. Таа станува тешка кога секоја нова функција мора да ја разбере внатрешната структура на три други услуги, две табели и потрошувач на редици што никој не сака да го допре. Резултатот сè уште може да се нарекува архитектура на микросервиси, но се однесува како дистрибуиран монолит: редоследот на распоредување е важен, малите промени се шират нашироко, а дефектите се пренесуваат подалеку од очекуваното.

Раздвојувањето не е апстрактна архитектонска доблест. Тоа е практичен начин промените да бидат поевтини, дефектите поограничени, а одговорноста појасна. Целта не е да се елиминираат зависностите. Таа е да се заменат кревките зависности од имплементацијата со намерни, стабилни договори.

Зошто споделените внатрешни детали создаваат кревки системи

Разгледајте услуга за нарачки на која ѝ се потребни информации за клиентите. Најбрзиот пат често е директно поврзување со базата на податоци за клиенти или увезување на ORM моделите на услугата за клиенти. Тоа функционира веднаш, но ја прави услугата за нарачки зависна од детали што никогаш не биле дизајнирани како јавен договор: имиња на табели, семантика на колони, миграции, настани на модели и конфигурација на рамката.

Штом тоа се случи, промена во шемата за клиенти што изгледа безопасно може да го прекине создавањето нарачки. Миграцијата на база на податоци сега бара координација меѓу тимови. На локалната развојна околина ѝ се потребни дополнителни акредитиви и шеми. Услугата може да изгледа здрава, додека неуспешно работи бидејќи табела што не ја поседува се променила под неа.

Истиот проблем се појавува кај спрегнатоста на ниво на код. Споделувањето PHP пакет што содржи доменски ентитети може да звучи уредно, но често значи дека еден ограничен контекст ги пренесува својот речник и правила во друг. Објектот Customer што е корисен за управување со идентитети не е автоматски соодветна претстава за наплата, исполнување или поддршка.

Дизајнирајте API-ја околу способности, а не околу складирање

Робусното API им кажува на потрошувачите што можат да побараат услугата да направи. Не треба да открива како услугата моментално ги складира своите податоци. Наместо да дозволите друга услуга да пребарува во табелата customers, изложете способност како „преземи подобност на клиент за наплата“ или „резервирај залиха за нарачка“.

Оваа разлика е важна бидејќи способноста може да остане стабилна додека нејзината имплементација се менува. Услугата за клиенти може да премине од една база на податоци во друга, да подели поле за адреса, да додаде кеширање или да повика надворешен добавувач за верификација. Потрошувачите не треба да знаат.

Одржувајте ги барањата и одговорите намерно мали. Враќањето на секое поле од внатрешен запис создава случајно ветување дека секое поле ќе остане значајно и достапно. Враќајте само што му е потребно на потрошувачот за својата одлука.

final class CheckoutCustomerResponse
{
    public function __construct(
        public readonly string $customerId,
        public readonly bool $canPlaceOrder,
        public readonly string $displayName,
    ) {
    }
}

Ваков вид одговор е помалку флексибилен во моментот, но потраен со текот на времето. Тој изразува случај на употреба, наместо да одразува ред.

Направете го договорот експлицитен

HTTP крајна точка е повеќе од рута и JSON товар. Нејзиниот договор вклучува автентикација, валидација, статусни кодови, идемпотентност, страничење каде што е релевантно, временски ограничувања и семантика на неуспех. Ако овие одлуки се остават имплицитни, клиентите измислуваат сопствени претпоставки.

На пример, крајна точка за создавање нарачка треба да разликува невалидно барање од проблем со преодна зависност. Неуспех поради валидација не треба повторно да се обидува; недостапна надолна услуга можеби треба. Одговорот треба јасно да ја покаже таа разлика.

POST /orders
Idempotency-Key: 8ac4f55e-7a97-4cc0-b4dc-c37d7386b49a

HTTP/1.1 201 Created
Content-Type: application/json

{
  "orderId": "ord_123",
  "status": "pending"
}

Ако истиот клуч за идемпотентност се испрати повторно по мрежен тајм-аут, услугата треба да го врати првичниот исход наместо да создаде втора нарачка. Ова не е козметичка API функција. Таа е суштинска кога повикувачите повторно се обидуваат по неизвесни неуспеси.

Верзионирајте воздржано

Верзионирањето не оправдува невнимателни промени што кршат компатибилност. Нова верзија на API е соодветна кога семантиката навистина мора да се промени, но многу промени се безбедно адитивни: воведете опционално поле, додадете нова крајна точка или поддржете нова enum вредност само кога клиентите се подготвени да се справат со неа.

  • Документирајте ги задолжителните полиња, опционалните полиња и стандардното однесување.
  • Дефинирајте кои одговори за грешка клиентите може да ги добијат и кои може повторно да се обидат.
  • Намерно застарувајте, со патека за миграција и познат датум на отстранување.
  • Тестирајте ги договорите независно од деталите на имплементацијата.

Користете асинхрони граници за работа на која не ѝ е потребен непосреден одговор

Не секоја интеракција припаѓа на патеката на барањето. Испраќањето потврда, ажурирањето аналитика, известувањето склад или синхронизирањето индекс за пребарување обично не треба да заврши пред API да може да потврди дека нарачката е прифатена.

Настаните може да ја намалат временската спрегнатост: услугата за нарачки ја запишува својата промена на состојба, а потоа објавува настан како order.created. Потрошувачите реагираат во свое време. Тоа ѝ дава на секоја услуга поголема контрола над достапноста и распоредувањето.

Но асинхроните системи заменуваат еден вид сложеност со друг. Пораките може да пристигнат повеќе од еднаш, да пристигнат доцна или да се обработат по друг поврзан настан. Потрошувачите треба да бидат идемпотентни, настаните треба да носат идентификатори и корисен контекст, а обработувачите треба да толерираат повторување. „Точно еднаш“ не е безбедна стандардна претпоставка за дистрибуиран работен тек.

За услуги базирани на база на податоци, моделот на трансакциско сандаче за испраќање често е прагматична заштита. Зачувајте ја доменската промена и излезниот настан во истата трансакција на базата на податоци, а потоа објавете го записот од сандачето одделно. Ова го спречува вообичаениот неуспех каде што потврдувањето на базата на податоци успева, но објавувањето на настанот не успева, оставајќи ги другите услуги неинформирани за промената.

Секоја услуга нека биде одговорна за сопствените податоци

Сопственоста на податоците е местото каде што раздвојувањето станува реално. Услугата треба да биде авторитет за податоците и правилата во својот домен. Другите услуги може да чуваат локални проекции или снимки, но не треба директно да запишуваат во табелите на таа услуга.

Моделите за читање може да бидат особено корисни за перформансите. Наместо да прави неколку синхрони повици при секое барање, услугата може да го одржува малиот подмножество од надворешни податоци што ѝ се потребни. Тоа може да значи прифаќање евентуална конзистентност, па бизнисот мора да одлучи каде е прифатлива малку застарена вредност и каде е потребна одлука во живо.

Docker и локалната оркестрација не го менуваат овој принцип. Споделениот docker-compose.yml може да го направи развојот погоден, но не треба тивко да стане причина секоја услуга да споделува бази на податоци, акредитиви или животни циклуси на распоредување. Алатките за погодност треба да ги поддржуваат границите, а не да ги бришат.

Градете за несовршени мрежи

Повик на метод во еден PHP процес е предвидлив. Мрежен повик не е. Тој може да биде бавен, делумно да не успее, да се врати откако повикувачот ќе истече поради тајм-аут или да успее додека одговорот се изгуби. Соодветно третирајте ја секоја оддалечена зависност.

  • Поставете експлицитни временски ограничувања за поврзување и барање.
  • Повторувајте само преодни неуспеси и користете ограничени повторувања со постепено зголемување на чекањето.
  • Избегнувајте автоматски повторувања за неидемпотентни операции, освен ако постои механизам за идемпотентност.
  • Користете прекинување на колото или постепена деградација кога зависноста е постојано недостапна.
  • Евидентирајте идентификатори за корелација и мерете ја латентноста на зависностите одделно од латентноста на апликацијата.

Овие практики не се само дефанзивно програмирање. Тие спречуваат една нездрава услуга да го потроши секој PHP работник, да ги наполни базените за поврзување и да претвори локализиран инцидент во поширок прекин.

Раздвојувањето е дисциплина на јасни ветувања

Најсилните граници меѓу услугите не се создаваат со избор на REST, редици, Docker или одредена PHP рамка. Тие произлегуваат од одлучувањето што ветува услугата, што поседува и на што ѝ е дозволено на нејзините потрошувачи да се потпираат.

Почнете со зависноста што предизвикува најголема болка од координацијата. Заменете го директниот пристап до табела со тесна способност. Дефинирајте однесување при неуспех. Додадете идемпотентност таму каде што се можни повторувања. Преместете ја несуштинската работа надвор од критичната патека. Секоја промена го прави системот малку помалку зависен од синхронизирано знаење.

Робусните API-ја прават повеќе од поврзување услуги. Тие им даваат на тимовите простор да еволуираат независно, што е вистинската придобивка од архитектура што трае.

Портрет на автор на блогот

Mihajlo

Јас сум Михајло - развивач поттикнат од љубопитност, дисциплина и постојаната желба да создадам нешто значајно. Споделувам увиди, упатства и бесплатни услуги за да им помогнам на другите да ја поедностават својата работа и да растат во постојано развивачкиот свет на софтверот и вештачката интелигенција.