Введение

Философия

Winter стоит на четырёх принципах. Они определяют, как выглядит код, и объясняют, почему фреймворк спокойно берётся в команду: меньше скрытого поведения, меньше рутины, предсказуемая производительность и отсутствие навязанных решений.

Явное лучше неявного

Поведение живёт в коде, а не в конфигах, которые надо держать в голове. Маршрут — атрибут на методе, привязка тела — атрибут на параметре, колонка таблицы — атрибут на свойстве сущности. Что видите, то и работает.

main/AuthController.php
#[RequestMapping('auth')]
class AuthController extends Controller
{
  #[PostMapping('login')]
  public function login(#[RequestJson, Valid] LoginRequest $req): ResponseEntity
  {
      // route, method, binding and validation — all visible right here
      return ResponseEntity::ok(/* ... */);
  }
}

Проверка простая: чтобы понять, что делает метод, не нужно открывать ни одного другого файла. Ни списка маршрутов, ни описания схемы, ни правил валидации в стороне — при переименовании метода расходиться нечему.

CLI вместо обвязки

Рутину пишет генератор. call make создаёт компоненты по шаблонам — сразу с правильным пространством имён и в правильном каталоге:

bash
php call make -c .User      # controller
php call make -s .User      # service
php call make -r .User      # repository
php call make -P .Import    # process
php call make -N .Queue     # daemon

Тот же call поднимает сервер, показывает таблицу маршрутов, гоняет миграции, запускает процессы и ставит автодополнение в shell. Один инструмент на весь жизненный цикл — см. Консоль.

Производительность по умолчанию

Быстро — это состояние по умолчанию, а не результат тюнинга.

Дорогое происходит один раз. Приложение резидентно: обход проекта, сборка контейнера и построение таблицы маршрутов случаются до того, как придёт первый запрос. Сам запрос ничего не поднимает — он только выполняется.

Поиск маршрута за O(1). Статические пути резолвятся по карте, динамические — объединённым регулярным выражением по чанкам, а не перебором маршрутов по одному.

Ожидание не блокирует. Запросы обслуживаются корутинами: пока один ждёт базу или внешний вызов, воркер выполняет другой. Одному воркеру не нужен поток на каждого клиента.

Кеш касается старта, а не запросов

При DEBUG=false список классов проекта сохраняется, и повторный старт не ходит по диску. При DEBUG=true кеш выключен, чтобы новый класс подхватывался без ручной пересборки.

Ни в одном из режимов сканирование не происходит на запросах — только при запуске. Поэтому изменение в коде видно после перезапуска приложения; в режиме call run dev перезапуск делается за вас, по изменению файла.

Механизм, а не политика

Фреймворк даёт конвейер и точки подключения к нему, но не решает за вас, что считать правильным.

Логгер вычищает значения по известным ключам — но не пытается угадать, что в вашей предметной области является секретом. Middleware получает запрос до контроллера — но какую именно проверку прав вы там сделаете, фреймворк не диктует. Есть пул соединений — а стратегию повторов при сбое выбираете вы.

Причина простая: политика, зашитая в фреймворк, верна ровно до первого проекта, где она неверна, — и тогда её приходится обходить. Механизм, отданный наружу, обходить не нужно.

Практическое следствие: почти всё в Winter — интерфейс или базовый класс, который вы заменяете своим. Если вам кажется, что фреймворк принял решение за вас, скорее всего рядом есть место, где оно переопределяется.

Что это даёт команде

Онбординг короче. Стереотипы — Controller, Middleware, Repository, Process, Daemon — задают одну и ту же структуру во всех проектах. Разработчик, знакомый с одним, читает второй сразу.

Меньше дрейфа. Нет параллельных конфигов, которые тихо расходятся с кодом: поведение и код — это один и тот же файл.

Ревью проще. Диффа контроллера достаточно, чтобы увидеть новый маршрут, его параметры и правила валидации. Ходить по репозиторию за контекстом не нужно.

Дальше