Престанете да ги препишувате API-јата: Архитектирајте за непроменлива бекенд-логика
Повеќето препишувања на API започнуваат со разумно барање: нова мобилна апликација, интеграција со партнер, редизајнирано плаќање или побрз екран за извештаи. Грешката е тоа ново барање да се третира како доказ дека деловната логика на backend-от мора да се изгради одново.
API е механизам за испорака. Вашите правила за цени, дозволи, залихи, наплата и транзиции на состојби се деловната суштина. Кога тие правила се испреплетени со контролери, формати на барања, ORM пребарувања и серилизација на одговори, секој нов клиент станува изговор да се препише код што требало да остане стабилен.
Целта не е засекогаш да го замрзнете системот. Таа е да го проектирате backend-от така што промените се случуваат на рабовите, додека основната логика останува разбирлива, тестирабилна и намерно едноставна.
Одделете го API што се менува од стабилните деловни правила
Корисна граница е едноставна: HTTP-грижите припаѓаат надвор од јадрото на апликацијата. Контролерите треба да преведат барање во влез што апликацијата го разбира, да повикаат случај на употреба и да го преведат резултатот во одговор. Тие не треба да одлучуваат дали нарачката може да се откаже или да пресметуваат попуст скриен во JSON payload.
Во PHP, тоа може да изгледа вака:
final class CancelOrderController
{
public function __invoke(
ServerRequestInterface $request,
CancelOrder $cancelOrder
): ResponseInterface {
$orderId = (string) $request->getAttribute('orderId');
$actorId = (string) $request->getAttribute('actorId');
$result = $cancelOrder->handle(
new CancelOrderCommand($orderId, $actorId)
);
return new JsonResponse([
'orderId' => $result->orderId,
'status' => $result->status,
]);
}
}
Контролерот го познава HTTP. Случајот на употреба CancelOrder ја познава операцијата. Правилата на доменот потоа може повторно да се користат од REST endpoint, задача од командна линија, queue consumer или иден GraphQL resolver, без копирање на политиката за откажување.
Оваа разлика исто така го прави верзионирањето на API помалку застрашувачко. Верзиониран endpoint може да прифаќа различни имиња на полиња или да враќа различен облик на одговор, додека двете верзии го повикуваат истиот случај на употреба на апликацијата. Верзионирајте го договорот кога е потребно; не го верзионирајте бизнисот само затоа што се сменила претставата.
Дизајнирајте околу случаи на употреба, а не околу табели во базата
API-ја обликувани според табели се примамливи бидејќи брзо се изложуваат: создадете endpoint за секој модел, прифатете ги сите постојни колони и оставете клиентот да составува работни текови. Таквиот пристап ја претвора шемата на базата во јавна одлука за производот.
Наместо тоа, моделирајте значајни дејства. POST /orders/{id}/cancel пренесува намера појасно од генерички endpoint за ажурирање што прифаќа {"status":"cancelled"}. Експлицитното дејство му дава на серверот едно место за да ги провери дозволите, состојбата, да ослободи резервации и да емитува каква било последователна работа.
Табелите во базата треба да го поддржуваат доменот, а не да го диктираат. API насочено кон клиентите може да враќа резиме на нарачка составено од неколку табели. Обратно, внатрешна табела може да содржи полиња за ревизија, имплементациски ознаки или преодни колони што никогаш не треба да ја преминат границата на API.
Одржувајте ги договорите експлицитни
На стабилната backend логика ѝ се потребни експлицитни влезови и излези. Избегнувајте да пренесувате необработени низи од барања длабоко во апликацијата, каде што опционалните полиња и претпоставките специфични за транспортот се шират без контрола. Користете command objects, value objects и именувани типови на резултати таму каде што додаваат јасност.
- Проверувајте ја синтаксата и задолжителните полиња на границата.
- Проверувајте ги деловните правила во случајот на употреба или моделот на доменот.
- Враќајте исходи на ниво на апликација, а не framework response objects.
- Мапирајте ги неуспесите на доменот во HTTP статусни кодови на границата на API.
На пример, „нарачката не е пронајдена“ и „нарачката не може да се откаже по испораката“ се различни исходи на апликацијата, дури и ако API избере различно да ги претстави. Одржувањето на таа разлика зачувува корисно однесување за секој интерфејс што го повикува случајот на употреба.
Насочете ги зависностите навнатре
Backend логиката станува кревка кога директно зависи од одреден двигател на база на податоци, библиотека за редици, cache client или framework model. Овие алатки се вредни, но треба да стојат зад интерфејси во сопственост на апликацијата.
Случајот на употреба може да зависи од OrderRepository и TransactionManager, додека инфраструктурен слој ги имплементира со PostgreSQL и PHP framework-от што веќе се користи. Јадрото треба да изрази што му е потребно, а не како тоа го извршува одреден адаптер.
Ова не е барање за сложена апстракција околу секоја библиотека. Интерфејс со една метода создаден само за да скрие стабилна алатка додава формалност без да донесе флексибилност. Воведете граница таму каде што зависноста влијае на деловното однесување, тестирањето, распоредувањето или трошокот за замена. Складирањето на податоци, давателите на плаќања, испораката на е-пошта и надворешните API-ја се вообичаени примери.
Трансакциите заслужуваат посебно внимание. Ако откажувањето на нарачка ја менува нејзината состојба и ослободува залиха, тие промени треба да се координираат во една трансакциска операција таму каде што базата го поддржува тоа. Ако операцијата мора и да објави настан, избегнувајте да претпоставувате дека commit во базата и објавување во message broker се едно атомско дејство. Outbox запис зачуван во истата трансакција на базата често е практичен начин да се евидентира работа што може сигурно да се испорача подоцна.
Користете миграции како еволуција, а не прекин
Промените на шемата се честа причина поради која тимовите чувствуваат дека се принудени на препишување. Побезбедниот пристап е промените да се направат компатибилни во фази: додадете ново складирање, запишувајте во двете претстави ако е потребно, дополнете ги постојните податоци, префрлете ги читањата, па отстранете ја старата патека дури откако повеќе не се користи.
Ова е важно во Dockerized deployments бидејќи контејнерите на апликацијата може независно да се заменуваат за време на rollout. Распоредување што бара секој контејнер да извршува нов код во точно истиот момент е кревко. Претпочитајте период во кој старите и новите верзии на апликацијата можат да работат со шемата.
Миграциите на базата треба да се извршуваат преку контролиран чекор на распоредување, а не автоматски од секој контејнер на апликацијата при стартување. Повеќе реплики што се натпреваруваат да ја применат истата миграција е оперативен проблем, а не архитектонска стратегија. Процесот на распоредување треба и да го направи неуспехот видлив и да запре пред да испорача код што бара миграција која не е завршена.
Тестирајте го јадрото таму каде што е најважно
Кога деловните правила се изолирани од HTTP и инфраструктурата, најважните тестови стануваат брзи unit или application tests. Тие можат да создадат нарачка, да извршат случај на употреба и да го потврдат исходот без да стартуваат веб-сервер или цел Docker stack.
Тоа не ги елиминира интеграциските тестови. Тие сè уште се потребни за repository queries, миграции, authentication middleware, серилизација и критични надворешни граници. Рамнотежата е важна: користете помал број реалистични интеграциски тестови за да го докажете поврзувањето и поголем број фокусирани тестови за да ги заштитите правилата.
И перформансите имаат корист. Бавните endpoint-и често се припишуваат на PHP или framework-от, кога вистинскиот проблем се неконтролирани шаблони на пребарувања, преголеми payload-и на одговори, повторени оддалечени повици или индекси што недостигаат. Чистата граница на апликацијата го олеснува лоцирањето на овие трошоци. Измерете ја вистинската патека на барањето, проверете го бројот и времетраењето на пребарувањата и оптимизирајте го конкретното тесно грло, наместо да расфрлате кешови низ кодната база.
Изградете backend што може да апсорбира барања
Непроменлив backend не значи недопрен backend. Тоа значи дека централните одлуки се менуваат бавно бидејќи се изразени во форма што преживува нови клиенти, нови endpoint-и, нови детали за складирање и нови грижи за распоредување.
Одржувајте го транспортниот код тенок. Направете ги деловните дејства експлицитни. Заштитете ја базата од тоа да стане ваше јавно API. Воведете инфраструктурни граници таму каде што го намалуваат реалниот ризик и развивајте ги шемите преку компатибилни чекори.
Тогаш следното барање за „сосема поинакво API“ станува она што обично е: нов адаптер околу систем чија највредна логика веќе е на вистинското место.