ИТ развој

Beyond the Commit: Architecting for Effortless PHP Refactoring

Надвор од Commit: Архитектура за лесно рефакторирање на PHP

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

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

Рефакторирањето почнува со наоѓање на вистинската граница

Добрите граници не се произволни папки. Тие ги одделуваат причините за промена. Правилото за цени не треба да разбира HTTP. Барањето кон базата на податоци не треба да одлучува што гледа API клиентот. Контролерот треба да ја координира работата, а не да стане место каде што се натрупува секое деловно правило.

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

Поотпорен дизајн ѝ дава на секоја грижа тесна улога:

  • Контролерите ги претвораат HTTP барањата во повици кон апликацијата и враќаат одговори.
  • Апликациските сервиси координираат случај на употреба, како што е поставување на нарачка.
  • Доменската логика ги поседува пресметките и деловните одлуки.
  • Репозиториумите или сервисите за барања ги изолираат деталите на перзистенцијата таму каде што таа апстракција е навистина корисна.
  • Инфраструктурните адаптери работат со бази на податоци, редици, даватели на е-пошта и надворешни API-ја.

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

Зависете од однесувањето, а не од погодностите на рамката

PHP рамките нудат продуктивни стандардни поставки, но погодноста може да стане спрегнатост кога објектите на рамката течат низ целата апликација. На пример, пренесувањето HTTP request објект во доменскиот код го прави деловното правило потешко за повторна употреба и тестирање. Исто така поттикнува скриени зависности од заглавија, сесии и глобална состојба.

Претпочитајте мали влезови што го опишуваат случајот на употреба. Команден објект може да биде обична PHP класа со експлицитни полиња. Апликацискиот сервис ја прима таа команда, ја валидира деловната операција и делегира на соработници чии одговорности се видливи во конструкторот.

final class PlaceOrder
{
    public function __construct(
        private OrderRepository $orders,
        private PriceCalculator $prices,
    ) {
    }

    public function handle(PlaceOrderCommand $command): Order
    {
        $total = $this->prices->calculate($command->items);

        $order = Order::create(
            $command->customerId,
            $command->items,
            $total,
        );

        $this->orders->save($order);

        return $order;
    }
}

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

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

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

Побезбедна промена во базата на податоци обично следи модел на проширување и повлекување:

  1. Додадете ја новата шема без да ја отстраните старата шема.
  2. Распоредете код што може да ја чита старата форма и да ја запишува новата форма, или намерно да ги синхронизира двете таму каде што е потребно.
  3. Пополнете ги постојните записи во контролирани серии.
  4. Префрлете ги читањата на новата претстава откако ќе ги валидирате податоците.
  5. Отстранете ја старата патека само откако сите поддржани верзии на апликацијата и worker-и повеќе не зависат од неа.

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

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

Одржувајте ја еволуцијата на API намерна

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

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

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

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

Docker може да ја подобри сигурноста при рефакторирање кога ги прави зависностите на апликацијата експлицитни. Корисна развојна поставеност ги декларира PHP runtime-от, екстензиите, базата на податоци, кешот и придружните сервиси, наместо да се потпира на што и да е инсталирано на лаптопот.

Но контејнерите не ги бришат разликите меѓу средините. Конфигурацијата треба да доаѓа од вредности специфични за средината, додека стандардните вредности и валидацијата остануваат во апликацијата. Контејнерот треба јасно да откаже кога недостига задолжителна поставка, наместо да стартува со случајна резервна вредност.

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

Тестовите треба да ги штитат одлуките, а не имплементациските ситници

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

Урамнотежената PHP стратегија за тестирање обично вклучува брзи единични тестови за деловни правила, интеграциски тестови за репозиториуми и граници на рамката и помал број end-to-end тестови за критични работни текови. Точната мешавина варира, но секој слој треба да одговори на различно прашање.

  • Дали правилото за цени го пресметува очекуваниот вкупен износ?
  • Дали репозиториумот правилно ги перзистира и презема записите според вистинското однесување на базата на податоци?
  • Дали клиентот може да го заврши суштинскиот API работен тек со валидна автентикација и грешки при валидација?

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

Оставете ја кодната база полесна за промена отколку што сте ја затекнале

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

Надвор од commit-от е местото каде што инженерската вредност се акумулира. Кога архитектурата ја претвора промената од ризично ископување во рутинска операција, тимот може да троши помалку енергија во одбрана на минатото и повеќе енергија во градење на она што му е потребно на производот понатаму.

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

Mihajlo

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