ИТ развој

Beyond Boilerplate: Architecting PHP APIs for Real-World Scale

Надвор од шаблонскиот код: Архитектура на PHP API-ја за реална скалабилност

Повеќето PHP API-а почнуваат со добри намери: рамка, неколку контролери, ORM модел и крајни точки што враќаат JSON. Тоа е доволно за да се докаже една идеја. Не е доволно за да поддржи производ низ растечки сообраќај, променливи барања, работа во заднина и тим што треба безбедно да го менува системот.

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

Започнете со модуларен монолит

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

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

final class PlaceOrder
{
    public function __construct(
        private OrderRepository $orders,
        private InventoryService $inventory,
        private TransactionManager $transactions,
    ) {}

    public function handle(PlaceOrderCommand $command): Order
    {
        return $this->transactions->run(function () use ($command) {
            $this->inventory->reserve($command->items);

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

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

            return $order;
        });
    }
}

Контролерот треба да преведе HTTP барање во команда и да го претвори резултатот во одговор. Тој не треба да одлучува за правила за залихи, да составува повеќечекорни запишувања или тивко да прави барања кон базата на податоци преку неповрзани модели. Тенките контролери не се естетска преференција; тие го олеснуваат тестирањето и повторната употреба на однесувањето од CLI команда, работник во редица или идна верзија на API.

Дизајнирајте ги договорите пред деталите на имплементацијата

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

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

Направете ги неуспесите читливи за машини

Корисникот не треба да мора да анализира проза за да одлучи што да направи понатаму. Вратете соодветен HTTP статус и структурирано тело со стабилен код за грешка. Деталите наменети за луѓе нека бидат корисни, но нека не бидат договорот.

{
  "error": {
    "code": "inventory_unavailable",
    "message": "Еден или повеќе побарани артикли не се достапни.",
    "details": {
      "items": ["sku-42"]
    }
  }
}

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

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

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

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

ORM-ите ја забрзуваат вообичаената работа, но не го отстрануваат однесувањето на базата на податоци. Внимавајте на N+1 барања при серијализирање колекции, неограничени множества резултати и вчитување цели записи кога се потребни само неколку полиња. Нетрпеливото вчитување може да реши еден проблем, а да создаде друг ако во меморијата повлече голем поврзан граф. Измерете го бројот на барања, избраните колони и обемот на редови за важните крајни точки.

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

Преместете ја бавната работа надвор од патеката на барањето

API барањето вообичаено треба да валидира, овластува, ја зачува суштинската состојба и да одговори. Испораката на е-пошта, обработката на слики, генерирањето извештаи, веб-куките и несуштинските интеграции припаѓаат во задачи во заднина.

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

Кога трансакција менува податоци и треба да активира задача, разгледајте образец outbox: запишете ја доменската промена и запис за настан во истата трансакција, а потоа нека работник објавува настани што чекаат. Ова го избегнува опасниот јаз каде што commit на базата на податоци успева, но процесот паѓа пред да ја стави последователната работа во редица.

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

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

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

Набљудувајте го системот што навистина го имате

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

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

Скалирајте ги одлуките, не само серверите

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

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

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

Mihajlo

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