ИТ развој

System Architecture: Designing APIs for Evolving Needs

Системска архитектура: Дизајнирање API-ја за потреби што се развиваат

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

Добрата системска архитектура не се обидува да ја предвиди секоја идна функционалност. Таа создава граници што ја прават промената намерна, забележлива и прифатлива по цена. За PHP backend тимовите, тоа обично значи да се третира API-то како нешто повеќе од рути и контролери: тоа е долгорочен договор меѓу клиентите, логиката на апликацијата, податоците и операциите.

Започнете со стабилни деловни концепти

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

Вообичаена рана грешка е директно изложување на структурите за перзистентност. Ако табелата orders содржи внатрешни знаменца, странски клучеви и оперативни временски ознаки, враќањето на тој ред како JSON ги поврзува клиентите со одлуките за складирање. Подоцнежното чистење на шемата тогаш станува настан што го нарушува API-то.

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

final class OrderResource
{
    public static function fromOrder(Order $order): array
    {
        return [
            'id' => (string) $order->id(),
            'status' => $order->status()->value,
            'total' => [
                'amount' => $order->total()->amount(),
                'currency' => $order->total()->currency(),
            ],
        ];
    }
}

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

Одржувајте ги слоевите искрени

Многу backend кодни бази започнуваат со тенки контролери и брзо натрупуваат валидација, авторизација, пресметки, SQL барања, известувања и логика за повторни обиди во истата акција. Крајната точка сè уште работи, но секој нов клиент или позадинска задача мора или да го дуплира однесувањето или интерно да повикува HTTP.

Практичната поделба е едноставна:

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

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

Дизајнирајте за дополнувања, не само за верзии

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

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

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

Користете експлицитна семантика

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

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

{
  "error": {
    "code": "validation_failed",
    "message": "The request contains invalid fields.",
    "fields": {
      "email": ["A valid email address is required."]
    }
  }
}

Заштитете ја базата на податоци од притисокот на API-то

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

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

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

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

Направете ја асинхроната работа видлива и обновлива

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

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

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

Распоредувајте ги промените како низа

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

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

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

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

Mihajlo

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