JavaRush /Курсы /Spring REST & MVC /RPC-like API vs REST: без команд

RPC-like API vs REST: без команд

Spring REST & MVC
3 уровень , 3 лекция
Открыта

1. Как API сползает в RPC-like стиль

Базовая грамматика у нас уже есть: коллекция, отдельный ресурс, GET/POST/PUT/PATCH/DELETE. Но бизнес почти никогда не приходит с формулировкой “добавьте ещё один PATCH”. Он говорит: завершить задачу, назначить исполнителя, прикрепить файл, удалить комментарий. И именно в этот момент API чаще всего снова сползает в каталог команд.

На уровне сервиса это нормально: внутри приложения completeTask() или uploadAttachment() звучат естественно. Проблема начинается тогда, когда те же глаголы становятся публичными путями вроде /completeTask и /uploadFileToTask. Внешний контракт снова описывает кнопки, а не объекты предметной области.

Здесь нас интересует практический вопрос: как перевести язык действий бизнеса в уже знакомую ресурсную грамматику.

RPC-like API: без религии и «священных войн»

Под RPC-like API здесь будем иметь в виду не конкретную технологию, а характерный паттерн контракта: путь отвечает на вопрос “что выполнить?”, а не “с чем работаем?”. Внешне разница обычно выглядит так:

Ось сравнения RPC-like подход (команды) Resource-like подход (ресурсы)
Главный смысл URI «Что сделать?» «С чем работаем?»
Как растёт API Новая фича → новый action endpoint Новая фича → уточнение ресурса/представления/подресурса
Как клиент «держит модель» Нужно помнить каталог команд Можно догадаться по структуре ресурсов
Самый частый HTTP-метод Обычно всё превращается в POST Методы используются по смыслу (GET, POST, PATCH, DELETE…)

Команды внутри приложения никто не запрещает. Вопрос только в том, должен ли клиент учить ваши имена методов вместо понятной карты ресурсов.

2. Когда API разрастается в каталог команд

Командный стиль почти всегда растёт одинаково: на каждую новую бизнес-просьбу появляется ещё один путь с глаголом. В Task Tracker API это быстро превращается во что-то вроде:

import java.util.List;

// Так выглядит "каталог команд": рост идёт добавлением новых глаголов в пути
List<String> endpoints = List.of(
    "/createTask",
    "/completeTask",
    "/assignTask",
    "/addCommentToTask",
    "/uploadFileToTask"
);

Проблема не только в количестве маршрутов. Клиенту приходится учить ваш каталог команд, документация распадается на список несвязанных действий, а API перестаёт выглядеть как карта домена. Как только вы это замечаете, следующий шаг почти напрашивается сам: взять каждое бизнес-действие и спросить, какой объект при этом появляется, меняется или читается.

3. Перевод действий в ресурсный мир

Теперь самое полезное: что делать, когда бизнес говорит “нужно действие X”, а мы хотим остаться в ресурсном стиле. Хороший прагматичный ответ почти всегда начинается не с выбора URI, а с простого вопроса: какой объект предметной области при этом появляется или меняется? Если вы честно на него ответили, дальше дизайн обычно сам “складывается”.

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

flowchart TD
    %% Идея: начинаем не с URI, а с вопроса "что за объект появляется/меняется?"
    A["Нам нужно действие (business operation)"] --> B{"Появляется новый объект?"}
    B -- Да --> C["Это создание ресурса → коллекция (например, comments/attachments)"]
    B -- Нет --> D{"Меняется состояние существующего объекта?"}
    D -- Да --> E["Это update ресурса → меняем представление (например, статус/исполнитель)"]
    D -- Нет --> F{"Нужно просто получить данные?"}
    F -- Да --> G["Это read ресурса/коллекции"]
    F -- Нет --> H["Проверь: не пытаемся ли мы спрятать сложный процесс за 'кнопкой'"]

Давайте посмотрим прямо на нашем домене. Вот как “команда” превращается в ресурсное решение без магии:

Представим команду: “Добавить комментарий к задаче”. Если мыслить командами, рука тянется сделать /addCommentToTask. Но если спросить “что появляется?”, ответ простой: появляется новый комментарий. Значит, комментарий — это ресурс (подресурс задачи), и у него есть место в API как у коллекции комментариев внутри задачи.

import java.util.Map;

// Ментальный перевод: "имя операции" -> "куда это ложится в ресурсной карте"
Map<String, String> operationToResource = Map.of(
    // Комментарий — новый объект, значит это создание элемента в коллекции comments
    "addCommentToTask", "/tasks/{taskId}/comments",
    // Вложение — тоже новый объект (метаданные + файл), значит коллекция attachments
    "uploadFileToTask", "/tasks/{taskId}/attachments"
);

Команда: “Загрузить файл к задаче”. Появляется новый объект — метаданные вложения. Значит, это тоже создание подресурса: коллекции вложений в контексте задачи.

Команда: “Назначить исполнителя”. Здесь новый объект не появляется, но меняется состояние задачи (например, поле assigneeName). Значит, это изменение существующего ресурса task. В ресурсной модели клиент работает с задачей, а назначение — это всего лишь изменение её состояния, а не “отдельная команда мира”.

Команда: “Завершить задачу” (complete). Если говорить честно, в нашем домене это, скорее всего, перевод статуса в DONE. Опять же: меняется состояние задачи, а не появляется отдельная сущность “Completion”. Значит, задача остаётся главным ресурсом, а “complete” — частный случай изменения её состояния.

Очень важная дисциплина: внутри сервиса вы можете продолжать использовать глагольные методы. Это нормально. Просто внешний API не должен копировать их имена.

class TaskService {
    void completeTask(String taskId) {
        // Внутри сервиса глагол естественен: это часть реализации бизнес-операции
        // Снаружи (в HTTP контракте) мы не хотим превращать это в адрес вида /completeTask
    }

    void assignTask(String taskId, String assigneeName) {
        // Аналогично: внутренняя команда ок
        // Снаружи это обычно будет update ресурса /tasks/{taskId} (например, поле assignee)
    }
}

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

4. Прагматичный REST: дисциплина без перфекционизма

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

Полезно сравнить три состояния, в которые часто попадают команды:

Стиль Как выглядит снаружи Что происходит внутри проекта
RPC-like хаос Много глагольных путей, “action endpoint” на каждую мелочь Клиент учит команды, API разрастается как список кнопок
Догматичный REST Всё строго “по учебнику”, но иногда чрезмерно сложно и неудобно Иногда появляются странные решения «ради правильности»
Прагматичный REST Ресурс в центре, действия выражаются через состояние/подресурс, а не через каталог команд API остаётся читаемым, но не мешает реальной разработке

И здесь важный момент: иногда в реальной коммерческой разработке встречаются операции, которые трудно выразить без “действия”. Например, “экспортировать отчёт” или “запустить пересчёт” — это может быть отдельный процесс, который живёт как ресурс “job”. Но обратите внимание: даже там хороший дизайн часто превращает действие в ресурс (“экспорт-задача”, “отчёт-экспорт”), а не в /doExportNow.

В Task Tracker API мы сознательно держим модель простой: основная история — tasks, а из вспомогательного — comments и attachments; tags — lookup-список значений. На этом масштабе прагматичный REST почти всегда означает одно: не плодить команды, если смысл можно выразить через ресурс, его состояние или подресурс.

5. Быстрый тест: признаки командного API

Когда вы проектируете API, полезно иметь быстрый инструмент самопроверки — как запах дыма на кухне. Не обязательно сразу понимать, что именно горит, но важно вовремя заметить, что “что-то точно идёт не так”. Для RPC-like-дизайна такой тест можно свести буквально к трём вопросам. И не надо превращать это в бюрократию: это просто короткая пауза перед тем, как вы добавите новый endpoint.

Первый вопрос звучит так: “Могу ли я в одном предложении сказать, какой ресурс адресую?” Если вы говорите “я вызываю completeTask”, то ресурс не прозвучал. Если вы говорите “я работаю с конкретной задачей”, ресурс прозвучал.

Второй вопрос: “Появляется ли новый объект?” Если да, чаще всего это коллекция — создание нового элемента, — и вы почти автоматически приходите к подресурсу или отдельной коллекции. Комментарий — отличный пример: он появляется, у него есть свой id, и он живёт в контексте задачи.

Третий вопрос: “Не добавляю ли я новую кнопку вместо того, чтобы описать изменение состояния?” Если вы делаете /changeStatus и /assignTask, это очень похоже на “кнопки”. Если вместо этого вы говорите “задача имеет статус и исполнителя, и мы меняем состояние задачи”, модель становится естественнее.

Можно даже сделать игрушечную проверку в коде — не ради “правильного алгоритма”, а просто чтобы мозг остановился на секунду:

record ApiCheck(String path) {
    boolean looksLikeCommand() {
        // Это не "валидатор REST", а грубая эвристика: глагол в начале пути = красный флажок
        return path.startsWith("/create")
            || path.startsWith("/update")
            || path.startsWith("/delete")
            || path.startsWith("/complete")
            || path.startsWith("/upload");
    }
}

// Пример: путь начинается с команды, значит эвристика срабатывает
ApiCheck a = new ApiCheck("/completeTask");
System.out.println(a.looksLikeCommand()); // true

Ещё раз: это не “детектор REST”. Это напоминалка: если вы видите, что путь начинается с глагола-команды, остановитесь и попробуйте переформулировать вопрос: “какой ресурс тут на самом деле?”

6. Типичные ошибки при попытке “избежать RPC-like”

Ошибка №1: перепутать “RPC-like плохо” с “глаголы запрещены вообще”.
Новички иногда берут правило “не делай /completeTask” и превращают его в догму “в URI не может быть глаголов никогда”. В результате начинается странная игра в эвфемизмы: путь не становится яснее, а просто делается менее честным. В этой лекции важен не запрет самого слова, а вопрос читаемости: виден ли ресурс и его место в модели.

Ошибка №2: заменять один action endpoint другим, не возвращаясь к ресурсу.
Иногда команда понимает, что /uploadFileToTask выглядит плохо, и делает, скажем, /taskFileUpload. Это косметика. Если вы не ответили на вопрос “это подресурс? новый объект? изменение состояния?”, вы просто поменяли вывеску на том же магазине команд.

Ошибка №3: пытаться “сделать REST” через механическое переименование, но оставить командную модель.
Бывает, что путь уже стал “похож на ресурс”, но внутри всё равно живёт команда: всегда POST, всегда разные странные формы входных данных, каждый endpoint живёт своей жизнью. REST — это не только “как назвать путь”, это ещё и про устойчивую модель операций вокруг ресурсов. Мы пока не уходим в детали реализации, но важно помнить: подмена словаря без смены мышления ничего не лечит.

Ошибка №4: воспринимать прагматичность как разрешение на хаос.
“Прагматичный REST” — это не “давайте как быстрее”. Это “давайте держать ресурсную модель и не усложнять её там, где это не даёт ценности”. Если вы под флагом прагматичности начинаете добавлять /doThis, /doThat, /doSomethingElse, вы просто возвращаетесь в RPC-like стиль, только теперь у вас ещё и моральная индульгенция.

Ошибка №5: забыть, что команды внутри приложения — это нормально, а наружу — нет.
Иногда разработчик начинает воевать с собственным кодом: “Раз REST, значит, и внутри нельзя писать completeTask()”. Не надо так. Внутри вы строите реализацию, и там глагольные методы — естественная вещь. Наружу вы строите контракт, и там важнее ресурсная карта и предсказуемость. Когда перед глазами есть компактная ресурсная карта проекта, эта граница становится гораздо заметнее: внутри могут жить команды сервиса, но наружу выходит не каталог кнопок, а карта предметной области.

1
Задача
Spring REST & MVC, 3 уровень, 3 лекция
Недоступна
Детектор командных путей
Детектор командных путей
1
Задача
Spring REST & MVC, 3 уровень, 3 лекция
Недоступна
Перевод действий бизнеса в ресурсный язык
Перевод действий бизнеса в ресурсный язык
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ