Ядро фреймворка
Что на самом деле делают за вас Laravel и Symfony — MVC, сервис-контейнер и автовайринг, middleware и события, маршрутизация и контроллеры, шаблонизаторы Blade и Twig, валидация, политики и голосующие для авторизации, а также аутентификация API.
23 вопросов
JuniorТеорияОчень частоВ чём разница между аутентификацией и авторизацией в веб-приложении?
В чём разница между аутентификацией и авторизацией в веб-приложении?
Аутентификация отвечает на вопрос кто вы: проверяет учётные данные и устанавливает текущего пользователя. Авторизация отвечает на вопрос можно ли вам это: проверяет, что уже опознанному пользователю позволено выполнить это действие над этим ресурсом. Аутентификация идёт первой — вход в систему ещё не даёт прав.
Типичные ошибки
- ✗Менять термины местами, называя шаг входа авторизацией
- ✗Считать вошедшего пользователя допущенным и проверять лишь наличие сессии
- ✗Проводить права в UI, пряча кнопки, и не делать никакой проверки на сервере
Уточняющие вопросы
- →Где в конвейере запроса вы бы проводили каждую из двух проверок и в каком порядке?
- →Почему проверка владения — своя ли это запись пользователя — относится к авторизации?
JuniorТеорияОчень частоЧто такое HTTP middleware и где оно стоит в конвейере обработки запроса фреймворка?
Что такое HTTP middleware и где оно стоит в конвейере обработки запроса фреймворка?
Middleware — это слой, оборачивающий цикл «запрос — ответ». Каждый получает запрос, может что-то с ним сделать, вызывает $next($request), передавая дальше по цепочке, и затем может изучить или изменить ответ на обратном пути. Стоит между ядром и контроллером — там живут авторизация, CORS и троттлинг.
Типичные ошибки
- ✗Думать, что middleware лишь постобрабатывает ответ и не видит запроса
- ✗Забыть вызвать
$next($request), из-за чего запрос молча проглатывается - ✗Не понимать, что middleware может оборвать цепочку, вернув ответ самостоятельно
Уточняющие вопросы
- →Как middleware отклоняет запрос заранее, так и не дойдя до контроллера?
- →Почему порядок регистрации двух middleware меняет поведение ответа?
JuniorТеорияОчень частоКак Laravel и Symfony реализуют архитектурный паттерн MVC при обработке запроса?
Как Laravel и Symfony реализуют архитектурный паттерн MVC при обработке запроса?
Роутер сопоставляет URL с действием контроллера. Контроллер координирует: тянет данные через модели — Eloquent-модели или сущности Doctrine — и отдаёт результат представлению (Blade или Twig) на отрисовку. Контроллер должен оставаться тонким; бизнес-правила живут в модели или сервисе, а не в действии.
Типичные ошибки
- ✗Позволять представлению самому запрашивать модели вместо данных, подготовленных контроллером
- ✗Класть бизнес-правила в действие контроллера, из-за чего оно разрастается в божественный объект
- ✗Думать, что
MVCописывает внутренности фреймворка, а не то, как вы раскладываете свой код
Уточняющие вопросы
- →Куда класть логику, которая не персистентность и не отображение — в сервис или в модель?
- →Чем обходится толстый контроллер, как только то же действие нужно запустить из очереди?
JuniorТеорияОчень частоКак ресурсный контроллер сопоставляет HTTP-глаголы действиям CRUD (создать-прочитать-обновить-удалить)?
Как ресурсный контроллер сопоставляет HTTP-глаголы действиям CRUD (создать-прочитать-обновить-удалить)?
Одно объявление порождает фиксированный набор именованных маршрутов на один контроллер: GET /posts вызывает index, POST /posts — store, GET /posts/{id} — show, PUT или PATCH /posts/{id} — update, а DELETE /posts/{id} — destroy. Действие выбирается парой из глагола и пути.
Типичные ошибки
- ✗Кодировать действие в пути (
/posts/delete/5) вместо использования HTTP-глагола - ✗Не знать, в какое имя метода отображается каждый глагол, из-за чего маршруты молча дают 404
- ✗Открывать все семь маршрутов, когда API нужны пять —
createиeditэто страницы форм
Уточняющие вопросы
- →Почему у API-ресурса нет
createиeditи как их исключить? - →Как route-model binding превращает
{id}в уже загруженную модель внутри действия?
JuniorТеорияОчень частоЧто делает сервис-контейнер фреймворка, когда контроллер объявляет зависимость по типу?
Что делает сервис-контейнер фреймворка, когда контроллер объявляет зависимость по типу?
Он сам её разрешает и создаёт. Контейнер читает типы в конструкторе, рекурсивно создаёт каждую зависимость и внедряет их, поэтому вы никогда не пишете new для соавтора руками. Привязки говорят, какой конкретный класс подставить вместо интерфейса, а привязка-синглтон переиспользует один экземпляр.
Типичные ошибки
- ✗Думать, что контейнеру нужен строковый ключ, хотя для разрешения хватает самого типа
- ✗Не привязать интерфейс к конкретному классу и потом удивляться, почему разрешение падает
- ✗Путать привязку-синглтон со статикой PHP — контейнер живёт в пределах одного запроса
Уточняющие вопросы
- →Почему синглтон в контейнере не доживает до следующего запроса под php-fpm?
- →Как внедрить скаляр вроде API-ключа, который контейнер не может вывести по типу?
JuniorТеорияОчень частоЧем сессионная аутентификация отличается от подписанного токена JWT (JSON Web Token) для API?
Чем сессионная аутентификация отличается от подписанного токена JWT (JSON Web Token) для API?
Сессия держит состояние на сервере: кука несёт только идентификатор, а сервер поднимает сессию по нему, поэтому отзыв доступа мгновенный, но нужны общее хранилище и защита от CSRF. JWT самодостаточен и не хранит состояния — сервер лишь проверяет подпись, что хорошо масштабируется, но отозвать его до истечения нельзя.
Типичные ошибки
- ✗Думать, что полезная нагрузка
JWTзашифрована — она лишь подписана, и её может прочесть любой - ✗Считать, что
JWTможно отозвать, хотя его останавливает только истечение или список запрета - ✗Забывать, что именно сессия на куках делает защиту от CSRF необходимой
Уточняющие вопросы
- →Как приблизить отзыв
JWT, не отказываясь полностью от отсутствия состояния? - →Почему хранение токена в
localStorageменяет риск CSRF на риск XSS?
JuniorТеорияЧастоЧем шаблонизаторы Blade и Twig различаются в том, что они позволяют делать представлению?
Чем шаблонизаторы Blade и Twig различаются в том, что они позволяют делать представлению?
Blade компилируется в обычный PHP, поэтому шаблон может вызвать любой PHP-код — удобно и легко злоупотребить. У Twig собственный изолированный синтаксис без сырого PHP вовсе, и вывод экранируется по умолчанию. Blade экранирует через {{ }}, а {!! !!} отключает это. Twig навязывает дисциплину, Blade вам доверяет.
Типичные ошибки
- ✗Считать, что
Bladeизолирует шаблон — скомпилированное представлениеBladeесть обычный PHP - ✗Хвататься за
{!! !!}для вывода пользовательского контента, вновь открывая дыру XSS - ✗Полагать, что ни один движок не экранирует по умолчанию, и экранировать каждое значение дважды
Уточняющие вопросы
- →Почему в проекте с внешними вёрстальщиками часто выбирают изолированный шаблонизатор
Twig? - →Какая логика не должна появляться в шаблоне, каким бы движком вы ни пользовались?
JuniorТеорияЧастоКак Symfony связывает URL с действием контроллера через атрибут #[Route]?
Как Symfony связывает URL с действием контроллера через атрибут #[Route]?
Вы помечаете действие: #[Route('/posts/{id}', name: 'post_show', methods: ['GET'])]. Symfony читает эти атрибуты рефлексией при прогреве кэша и компилирует их в таблицу маршрутов, поэтому во время выполнения сопоставление — это поиск, а не перебор. Плейсхолдеры вроде {id} приходят аргументами действия.
Типичные ошибки
- ✗Думать, что атрибуты разбираются на каждый запрос, а не компилируются в кэшированную таблицу
- ✗Забыть
methods:, из-за чего одно действие отвечает на любой глагол, включаяDELETE - ✗Не давать маршруту имя, а затем хардкодить URL вместо генерации по имени
Уточняющие вопросы
- →Почему после добавления атрибута нужно сбросить кэш маршрутов и что строит прогрев?
- →Как ограничить
{id}цифрами и что произойдёт с запросом, не прошедшим ограничение?
JuniorТеорияЧастоКак валидатор Symfony проверяет объект, размеченный атрибутами-ограничениями?
Как валидатор Symfony проверяет объект, размеченный атрибутами-ограничениями?
Правила объявляются на свойствах — #[Assert\NotBlank], #[Assert\Email] — и вызывается $validator->validate($dto). Он обходит эти метаданные рефлексией, выполняет каждое ограничение и возвращает ConstraintViolationList. Объект остаётся обычным DTO; правила путешествуют вместе с ним, а не с контроллером.
Типичные ошибки
- ✗Ждать, что ограничения сработают при присваивании, а не при явном вызове
validate() - ✗Считать, что первое нарушение прерывает проверку —
validate()собирает все нарушения в список - ✗Валидировать сущность вместо DTO запроса, из-за чего плохой ввод доходит до доменного объекта
Уточняющие вопросы
- →Как группы валидации позволяют одному DTO нести разные правила для создания и обновления?
- →Где применять правило, требующее обращения к базе, например уникальность email?
MiddleТеорияЧастоПочему автовайринг падает, когда у интерфейса две реализации, и как это чинится?
Почему автовайринг падает, когда у интерфейса две реализации, и как это чинится?
Контейнер разрешает по типу. Когда PaymentGatewayInterface реализуют два класса, он не может выбрать и падает на этапе compile — громко, а не молча берёт первый попавшийся. Лечится явной привязкой: alias или #[Autowire] в Symfony, bind() в Laravel.
Типичные ошибки
- ✗Считать, что контейнер молча выберет одну из реализаций, а не упадёт с ошибкой
- ✗Указывать везде конкретный класс вместо того, чтобы один раз привязать интерфейс
- ✗Забывать, что двум потребителям законно могут быть нужны разные реализации
Уточняющие вопросы
- →Двум контроллерам нужны разные шлюзы — как это выражает контекстная привязка Laravel?
- →Когда стоит внедрить итерируемый список всех реализаций вместо привязки одной?
MiddleТеорияЧастоКак события и слушатели Laravel отделяют побочные эффекты от вызвавшего их действия?
Как события и слушатели Laravel отделяют побочные эффекты от вызвавшего их действия?
Действие диспатчит доменное событие вроде OrderPlaced и ничего не знает о том, кто на него отреагирует. Каждый побочный эффект — отдельный listener, тестируемый в одиночку, и может реализовать ShouldQueue, чтобы выполниться вне запроса. Плата: поток управления становится неявным.
Типичные ошибки
- ✗Держать побочные эффекты прямо в контроллере, из-за чего каждый новый снова правит действие
- ✗Считать listener асинхронным по умолчанию — только
ShouldQueueуносит его из запроса - ✗Ждать, что падение очередного listener откатит действие, которое диспатчило событие
Уточняющие вопросы
- →Что интерфейс-маркер очереди
ShouldQueueменяет в обработке падений слушателя? - →Как сохранить обозримость слушателей события, если контроллер их больше не называет?
MiddleТеорияЧастоЧто делает form request в Laravel до того, как выполнится тело метода контроллера?
Что делает form request в Laravel до того, как выполнится тело метода контроллера?
Метод типизируется через FormRequest, и контейнер его разрешает: до выполнения тела он вызывает authorize(), а затем rules(). Нарушение бросает ValidationException — 422 для API или редирект назад с ошибками для веб-формы, — поэтому тело видит только $request->validated().
Типичные ошибки
- ✗Читать в методе
$request->all()вместоvalidated(), из-за чего проходят непроверенные ключи - ✗Понимать
authorize()как проверку аутентификации, а не как проверку прав на этот запрос - ✗Ждать от
rules()списка ошибок в методе, хотя нарушение бросаетValidationException
Уточняющие вопросы
- →Чем ответ при неудаче отличается для API-клиента и для браузерной формы и почему?
- →Как оставить один
FormRequestпригодным и для создания, и для обновления с разными правилами?
MiddleТеорияЧастоЧто такое Facade в Laravel под капотом и в чём честная критика этого подхода?
Что такое Facade в Laravel под капотом и в чём честная критика этого подхода?
Это не статический класс: Cache::get() попадает в __callStatic, который достаёт настоящий объект из контейнера и перенаправляет вызов. Критика в том, что он прячет зависимости: конструктор больше не объявляет, что нужно классу, и тесты уходят в статические моки.
Типичные ошибки
- ✗Думать, что Facade — настоящий статический класс, а не прокси через
__callStaticк контейнеру - ✗Читать конструктор, чтобы узнать соавторов класса, хотя фасады их прячут
- ✗Называть фасады тестируемыми, пока тест статически мокает вместо внедрения двойника
Уточняющие вопросы
- →Когда Facade допустим, а когда скрытая связанность оправдывает внедрение через конструктор?
- →Чем
Cache::shouldReceive()отличается от внедрения тестового двойника контракта?
MiddleТеорияЧастоКак политика Laravel решает, вправе ли пользователь действовать над конкретной моделью?
Как политика Laravel решает, вправе ли пользователь действовать над конкретной моделью?
Класс политики привязан к одной модели; её методы — это права с сигнатурой (User $user, Post $post): bool. Вызывается она как $this->authorize('update', $post) или через middleware can:, а отказ бросает AuthorizationException → 403. Экземпляр даёт проверить владение.
Типичные ошибки
- ✗Проверять только роль, хотя метод политики получает и сам экземпляр модели для проверки владения
- ✗Забывать, что
authorize()бросает, а не возвращает false — при отказе метод не выполнится - ✗Считать, что регистрация политики сама защищает маршруты без вызова
authorize()или middlewarecan:
Уточняющие вопросы
- →Что меняет хук раннего решения
before()в политике и как он может выдать лишнее? - →Как проверить право, у которого ещё нет экземпляра модели, например создание поста?
MiddleТеорияЧастоЧто такое service provider в Laravel и чем register() отличается от boot()?
Что такое service provider в Laravel и чем register() отличается от boot()?
Провайдер — единица начальной загрузки. В register() можно только делать bind в контейнер, но нельзя разрешать сервис: другие провайдеры могли ещё не зарегистрироваться. boot() идёт после всех, там уже можно добавлять маршруты и слушателей. Deferred грузятся лениво.
Типичные ошибки
- ✗Разрешать другой сервис прямо в
register(), пока его провайдер ещё не зарегистрирован - ✗Регистрировать маршруты или слушателей событий в
register()вместоboot() - ✗Думать, что deferred-провайдер грузится на каждый запрос, а не при первом обращении
Уточняющие вопросы
- →Зачем deferred-провайдеру объявлять
provides()и что ломается, если список неверен? - →Как упорядочить два провайдера, если
boot()одного зависит от bind другого?
MiddleТеорияЧастоЧем middleware в Laravel отличается от слушателя событий ядра в Symfony?
Чем middleware в Laravel отличается от слушателя событий ядра в Symfony?
Middleware — конвейер декораторов: слой получает запрос и замыкание $next, работает до и после него, а обрывает цепочку, не вызвав $next, поэтому порядок осмыслен. Слушатель ядра же уведомляется на событиях жизненного цикла и обрывает цепочку, выставив ответ.
Типичные ошибки
- ✗Считать порядок middleware косметикой, хотя позиция в стеке осмысленна
- ✗Ждать, что слушатель ядра обернёт следующий слой, а не будет уведомлён в точке
- ✗Не знать, что слушатель обрывает цепочку, выставив ответ в объект события
Уточняющие вопросы
- →Какие события ядра срабатывают —
kernel.request,kernel.response,kernel.exception— и в каком порядке? - →Как terminable middleware выполняет работу уже после того, как ответ отправлен?
MiddleТеорияЧастоКогда вы выберете пакет API-аутентификации Laravel Sanctum, а не его собрата Passport?
Когда вы выберете пакет API-аутентификации Laravel Sanctum, а не его собрата Passport?
Sanctum лёгкий: непрозрачные токены в базе, несущие abilities, которые проверяются при авторизации, плюс режим на куках для SPA того же домена. Passport — полноценный OAuth2-сервер. Своей SPA или своему приложению хватит Sanctum; сторонним клиентам нужен Passport.
Типичные ошибки
- ✗Тянуть OAuth2-машинерию Passport, когда войти нужно лишь своей SPA
- ✗Считать токены Sanctum
JWT— это непрозрачные строки, которые ищутся в базе - ✗Выдавать каждый токен полнодоступным и никогда не проверять его abilities в методе
Уточняющие вопросы
- →Что дают abilities токена сверх того, что даёт проверка роли самого пользователя?
- →Почему режиму Sanctum на куках для SPA того же домена всё равно нужна защита от CSRF?
MiddleТеорияЧастоКак сервис-контейнер Symfony автовайрит конструктор контроллера?
Как сервис-контейнер Symfony автовайрит конструктор контроллера?
Он разрешает каждый параметр конструктора по type-hint через рефлексию, на этапе compile контейнера, при autowire: true в services.yaml. Результат — скомпилированный PHP-класс контейнера, а не поиск на каждый запрос, поэтому ошибка связывания всплывает при прогреве кеша.
Типичные ошибки
- ✗Думать, что контейнер рефлексирует на каждый запрос, а не компилируется один раз
- ✗Ждать ошибку связывания на боевом трафике, а не на этапе прогрева кеша
- ✗Считать, что контейнер сопоставляет по имени параметра, а не по его type-hint
Уточняющие вопросы
- →Как внедрить скаляр вроде API-ключа, который автовайринг не выведет по типу?
- →Что PHP-атрибут
#[Autowire]переопределяет для одного параметра, чего не можетservices.yaml?
MiddleТеорияЧастоЧем Voter в Symfony отличается от Policy в Laravel при проверке прав?
Чем Voter в Symfony отличается от Policy в Laravel при проверке прав?
Политика привязана к одному классу модели: её методы — это права, и отвечает ровно одна политика. Voter же ни к какому классу не привязан — он объявляет supports($attribute, $subject), а access decision manager опрашивает каждый voter и сводит голоса по стратегии.
Типичные ошибки
- ✗Ждать, что voter выбирается как политика — менеджер опрашивает каждый зарегистрированный voter
- ✗Не замечать стратегию решения: при
affirmativeпо умолчанию хватает одного голоса за - ✗Считать, что voter не видит субъекта, и решать только по ролям пользователя
Уточняющие вопросы
- →Когда стратегия решения
unanimousуместна и что ломается, если перейти на неё поздно? - →Как совместить два voter, расходящихся во мнении об одном атрибуте на одном субъекте?
SeniorДизайнЧастоБольшое приложение на Laravel/Symfony отвечает ошибками во всех мыслимых формах. Часть эндпоинтов отдаёт HTTP 200 с телом {"error": ...}, часть выпускает наружу HTML со стектрейсом, ошибки валидации приходят в одной JSON-форме, а ошибки аутентификации — в другой, и каждый клиент обзавёлся своим хрупким парсером под каждую. Спроектируйте единый контракт ошибок на всё приложение. Он обязан покрывать ошибки валидации (422), аутентификации и авторизации (401/403), отсутствие ресурса (404) и неожиданные внутренние сбои (500); он не должен раскрывать в продакшене внутренности — стектрейсы, SQL, пути файлов; и он должен принуждаться ровно в одном месте, а не памятью каждого разработчика о договорённости. Скажите, где это место, как выглядит полезная нагрузка и как новое доменное исключение получает свой статус-код, ничего не сообщая контроллеру.
Большое приложение на Laravel/Symfony отвечает ошибками во всех мыслимых формах. Часть эндпоинтов отдаёт HTTP 200 с телом {"error": ...}, часть выпускает наружу HTML со стектрейсом, ошибки валидации приходят в одной JSON-форме, а ошибки аутентификации — в другой, и каждый клиент обзавёлся своим хрупким парсером под каждую. Спроектируйте единый контракт ошибок на всё приложение. Он обязан покрывать ошибки валидации (422), аутентификации и авторизации (401/403), отсутствие ресурса (404) и неожиданные внутренние сбои (500); он не должен раскрывать в продакшене внутренности — стектрейсы, SQL, пути файлов; и он должен принуждаться ровно в одном месте, а не памятью каждого разработчика о договорённости. Скажите, где это место, как выглядит полезная нагрузка и как новое доменное исключение получает свой статус-код, ничего не сообщая контроллеру.
Рендерите ошибки в ОДНОМ месте — в обработчике исключений, — который сопоставит каждому доменному исключению статус-код. Отдавайте один конверт (application/problem+json): валидация и авторизация различаются полями, а не формой. Исход несёт статус, а не 200. Стектрейс — в лог.
Типичные ошибки
- ✗Отдавать HTTP
200с объектом ошибки, вынуждая каждого клиента разбирать тело, чтобы понять провал - ✗Форматировать ошибки внутри каждого контроллера — форма расползается, и принудить её нечем
- ✗Пускать стектрейс в продакшен-нагрузку вместо лога
Уточняющие вопросы
- →Как написанное сегодня доменное исключение получит
409, ничего не сообщая контроллеру? - →Что ломается у клиентов при добавлении поля в конверт и что — при переименовании поля?
SeniorДизайнЧастоВы поддерживаете публичный REST API, которым пользуются сторонние интеграторы: их код вы не видите и выкатить за них ничего не можете. Предстоит ломающее изменение ресурса заказа: total перестаёт быть простым числом и становится объектом денег, а два поля исчезают. На столе три варианта: версионирование в URI (/v1/orders, /v2/orders), версионирование через media-type в заголовке Accept (application/vnd.acme.v2+json) и параметр запроса ?version=2. Выберите один и защитите выбор. Разберите, как не дублировать все контроллеры и маршруты между версиями, сколько живёт v1 и на каких данных это решается, как узнать, кто из интеграторов вообще ещё зовёт v1, и как не дать двум версиям расщепить всё приложение на две кодовые базы, которые придётся править дважды.
Вы поддерживаете публичный REST API, которым пользуются сторонние интеграторы: их код вы не видите и выкатить за них ничего не можете. Предстоит ломающее изменение ресурса заказа: total перестаёт быть простым числом и становится объектом денег, а два поля исчезают. На столе три варианта: версионирование в URI (/v1/orders, /v2/orders), версионирование через media-type в заголовке Accept (application/vnd.acme.v2+json) и параметр запроса ?version=2. Выберите один и защитите выбор. Разберите, как не дублировать все контроллеры и маршруты между версиями, сколько живёт v1 и на каких данных это решается, как узнать, кто из интеграторов вообще ещё зовёт v1, и как не дать двум версиям расщепить всё приложение на две кодовые базы, которые придётся править дважды.
Версионирование в URI — прагматичный выбор по умолчанию: оно видно, кэшируется, маршрутизируется. Media-type чище в теории, но враждебен клиентам и кэшам. Версионируйте контракт, а не кодовую базу: один слой бизнес-логики, меняется лишь сериализатор. Токены на клиента показывают, кто ещё зовёт v1.
Типичные ошибки
- ✗Расщеплять всё приложение на ветки
v1иv2вместо того, чтобы менять только сериализатор - ✗Выбирать media-type для публичного API, чьи клиенты, кэши и браузеры им не пользуются
- ✗Назначать дату sunset, не имея данных по клиентам о том, кто ещё зовёт
v1
Уточняющие вопросы
- →Как удержать один контроллер, обслуживающий обе версии, без ветвлений вида
if ($version === 2)? - →Что версия сообщает клиенту такого, чего не понадобилось бы при чисто аддитивном изменении?
SeniorДизайнИногдаАктивно используемый публичный эндпоинт надо вывести из эксплуатации. Его всё ещё зовут десятки сторонних интеграций: их код вы не видите, выкатить за них ничего не можете, а у поддержки нет даже списка, кто это. Ограничения жёсткие: ни одна интеграция не должна сломаться без предупреждения; прежде чем удалять, нужны данные о том, кто ещё зовёт эндпоинт; и команда хочет твёрдую дату удаления, а не вечную поддержку. Спроектируйте раскатку целиком. Разберите, что вы отдаёте вызывающим, пока эндпоинт ещё жив, как выяснить, кто на самом деле остался среди вызывающих, как достучаться до них, когда записи в changelog явно мало, и что вы сделаете в последние недели, чтобы вытряхнуть интеграции, которые вообще ничего у вас не читают.
Активно используемый публичный эндпоинт надо вывести из эксплуатации. Его всё ещё зовут десятки сторонних интеграций: их код вы не видите, выкатить за них ничего не можете, а у поддержки нет даже списка, кто это. Ограничения жёсткие: ни одна интеграция не должна сломаться без предупреждения; прежде чем удалять, нужны данные о том, кто ещё зовёт эндпоинт; и команда хочет твёрдую дату удаления, а не вечную поддержку. Спроектируйте раскатку целиком. Разберите, что вы отдаёте вызывающим, пока эндпоинт ещё жив, как выяснить, кто на самом деле остался среди вызывающих, как достучаться до них, когда записи в changelog явно мало, и что вы сделаете в последние недели, чтобы вытряхнуть интеграции, которые вообще ничего у вас не читают.
Объявить в самом ответе, измерить, затем удалить. Отдавайте заголовки ответа Deprecation и Sunset, чтобы срок ехал с каждым ответом. Middleware пишет идентификатор клиента на каждый вызов — так появляется кривая использования по клиентам. Перед датой сделайте короткий brownout из 410.
Типичные ошибки
- ✗Тихо менять поведение старого эндпоинта на месте — это ломает вызывающих вообще без сигнала
- ✗Объявлять дату удаления, не имея данных по клиентам о том, кто на самом деле сломается
- ✗Верить, что changelog дойдёт до интеграций, которые его не читают, и пропускать brownout
Уточняющие вопросы
- →Что заголовок ответа
Sunsetдаёт клиенту такого, чего не даёт та же дата в changelog? - →Сколько должен длиться brownout и как отличить сломанную интеграцию от заброшенной?
SeniorДизайнИногдаДвум внутренним сервисам нужно интегрироваться, и команда разрывается между REST, языком запросов GraphQL и асинхронной очередью сообщений вроде RabbitMQ. Путей два. На первом сервис рендерит страницу и ему прямо сейчас нужен текущий баланс клиента, с фиксированным и заранее известным набором полей, прежде чем он ответит браузеру. На втором около 50 000 уведомлений user profile changed в день должны дойти до сервиса индексации: ответ вызывающему не нужен, потребителя регулярно гасят на технические работы, и ни одно событие не должно потеряться. Выберите механизм для каждого пути и защитите выбор через связанность и семантику отказов, а не через вкусы и не через то, что команде приятнее писать. Явно скажите, что происходит на каждом пути, когда вызываемая сторона недоступна.
Двум внутренним сервисам нужно интегрироваться, и команда разрывается между REST, языком запросов GraphQL и асинхронной очередью сообщений вроде RabbitMQ. Путей два. На первом сервис рендерит страницу и ему прямо сейчас нужен текущий баланс клиента, с фиксированным и заранее известным набором полей, прежде чем он ответит браузеру. На втором около 50 000 уведомлений user profile changed в день должны дойти до сервиса индексации: ответ вызывающему не нужен, потребителя регулярно гасят на технические работы, и ни одно событие не должно потеряться. Выберите механизм для каждого пути и защитите выбор через связанность и семантику отказов, а не через вкусы и не через то, что команде приятнее писать. Явно скажите, что происходит на каждом пути, когда вызываемая сторона недоступна.
Синхронное чтение с фиксированным контрактом — вызов REST. А 50 тысяч уведомлений без ответа — на очередь: производителю всё равно, что потребитель лежит, а долговечность и повторы даёт только брокер. GraphQL окупается лишь на многих формах одного графа и стоит кэширования и авторизации по полям.
Типичные ошибки
- ✗Выбирать один механизм на всю интеграцию, хотя у двух путей разная семантика отказов
- ✗Считать, что цикл повторов на стороне вызывающего даёт ту же долговечность, что и брокер
- ✗Брать
GraphQLпо умолчанию и расплачиваться кэшированием и авторизацией по полям
Уточняющие вопросы
- →Кто отвечает за дубль уведомления, если брокер гарантирует лишь доставку не менее одного раза?
- →Что должно стать правдой про путь рендера страницы, чтобы очередь там обрела смысл?