JavaRush /Курси /Spring REST & MVC /Фінальна карта ендпоінтів Task Tracker API

Фінальна карта ендпоінтів Task Tracker API

Spring REST & MVC
Рівень 4 , Лекція 4
Відкрита

1. Endpoint-map до коду

Карту ендпоінтів краще зібрати ще до коду. Якщо залишити це «на потім», дуже швидко виявиться, що у вас є /tasks/list і /getTasks, які роблять майже те саме. Це як ремонт із фразою «та ми на місці вирішимо»: зазвичай виходить дорожче.

Endpoint-map — це «карта метро» вашого API. Вона фіксує, які адреси існують, які методи до них застосовні та яка зона відповідальності в кожного шляху. Нам важливо зробити це до коду з простої причини: код можна переписати, контролери можна перейменувати, а от публічні адреси й сама форма API — це те, що клієнти запам'ятовують як контракт.

Є й друга практична причина: карта ендпоінтів допомагає узгодити команду — навіть якщо команда це ви і ваше майбутнє «ви» через два тижні. Сьогодні ви проєктуєте /api/v1/tasks/{taskId}/attachments/{attachmentId}/download, а завтра вже не доведеться сперечатися із самим собою, чому раптом захотілося /downloadAttachment. Карта — це «точка істини», щоб не плодити адреси навмання.

І нарешті, карта ендпоінтів дає змогу одразу виділити виключені антиприклади. Не просто «не робіть так», а «у канонічну карту це не входить». Це і є дисципліна. А дисципліна — це коли ви робите правильно навіть у п’ятницю ввечері, а не лише в понеділок уранці.

2. Формат ендпоінтів

Щоб карта не перетворилася на клуб суперечок про назви, спочатку фіксуємо формат запису. Коли люди не домовляються про формат, вони починають сперечатися про пробіли, тире й про те, «а давайте getAllTasks назвемо fetchTasks — так модніше». Щоб не перетворювати проєкт на клуб любителів найменувань, ми обираємо простий формат: HTTP_METHOD /path і поруч короткий зміст — «що це таке».

Зверніть увагу: ми зараз фіксуємо лише карту адрес і методів. Ми не обговорюємо DTO, валідацію, статус-коди та формат відповідей — це окремі шари контракту. Тут нам важливо, щоб будь-яка людина, і особливо ви самі, могла відкрити карту й одразу зрозуміти: «Які входи є в системи та як вони називаються?».

Цього вже достатньо, щоб карта однаково читалася людиною, .http-сценарієм і майбутнім мапінгом контролера. Окремі допоміжні класи й demo-об’єкти тут нічого не додають: головний артефакт — це сама узгоджена таблиця адрес.

Перш ніж дивитися на повну карту, корисно ще раз назвати чотири рішення, з яких вона складається:

  • на верхньому рівні карти — tasks і lookup-ресурс tags;
  • comments і attachments живуть як підресурси задачі;
  • зміни стану не їдуть у командні URI, а /download залишається вузьким винятком для бінарного вмісту;
  • усі адреси починаються з /api/v1.

Нижче — канонічна карта ендпоінтів проєкту. Це наш робочий орієнтир: коли дійде до @RequestMapping і методів контролера, адреси вже не доведеться вигадувати заново.

3. Фінальна endpoint-map Task Tracker API

Тепер лишилося зібрати все в одну карту. Уявіть, що ви малюєте план евакуації з будівлі: важливо не лише знати, де вихід, а й те, щоб усі виходи були підписані однаково, а зайві двері «до комірчини» випадково не позначили як «пожежний вихід».

Карту зручніше читати за ресурсними зонами. У нас є основний ресурс tasks, у нього — допоміжні підресурси comments і attachments, а також окремий lookup-ресурс tags. Усі шляхи починаються з одного базового префікса /api/v1, а ідентифікатори ресурсів ми позначаємо через {taskId}, {commentId}, {attachmentId} — це плейсхолдери, а не реальні значення.

Для наочності — схема відносин (так, це «картинка зі слів», але вона дуже допомагає мозку):

flowchart TD
    API["/api/v1"] --> TASKS["/tasks"]
    TASKS --> TASK_ID["/{taskId}"]
    TASK_ID --> COMMENTS["/comments"]
    COMMENTS --> COMMENT_ID["/{commentId}"]
    TASK_ID --> ATT["/attachments"]
    ATT --> ATT_ID["/{attachmentId}"]
    ATT_ID --> DL["/download"]
    API --> TAGS["/tags"]

Нижче — повний список endpoint’ів у канонічній версії проєкту.

tasks: колекція задач і окрема задача

Із задач усе починається, тому тут особливо важливо, щоб шлях був передбачуваним. Колекція задач — це «місце, де живуть задачі», а шлях із {taskId} — адреса конкретної задачі. CRUD-операції виражаються методами HTTP, а не додатковими дієсловами в URL. Так клієнту простіше: він бачить один ресурс і різні операції над ним.

HTTP Шлях Зміст (людською мовою)
GET /api/v1/tasks Отримати список задач
POST /api/v1/tasks Створити нову задачу
GET /api/v1/tasks/{taskId} Отримати одну задачу за ідентифікатором
PUT /api/v1/tasks/{taskId} Повністю замінити стан задачі, що змінюється
PATCH /api/v1/tasks/{taskId} Частково оновити задачу
DELETE /api/v1/tasks/{taskId} Видалити задачу

Зверніть увагу на дисципліну: всюди використовується tasks у множині, немає task, Task, myTasks та інших творчих поривів. Це нудно, зате стабільно. А стабільність в API — дуже недооцінена радість, приблизно як стабільний інтернет.

comments: допоміжний підресурс у межах задачі

Коментарі в нашому проєкті — не «окремий великий всесвіт», а допоміжний підресурс, який має сенс лише в контексті задачі. Тому їхня адреса містить {taskId}. Такий шлях не лише гарно виглядає, а й допомагає одразу зрозуміти межу: коментарі належать задачі, і без задачі ми їх не розглядаємо.

HTTP Шлях Зміст (людською мовою)
GET /api/v1/tasks/{taskId}/comments Отримати коментарі задачі
POST /api/v1/tasks/{taskId}/comments Додати коментар до задачі
DELETE /api/v1/tasks/{taskId}/comments/{commentId} Видалити коментар задачі

Зверніть увагу, що у нас немає PUT /comments/{commentId} та інших операцій «редагувати коментар». Це не тому, що так не можна, а тому, що ми свідомо тримаємо проєкт в адекватному обсязі: допоміжний підресурс — це підтримка, а не другий головний ресурс, який перетягує всю увагу.

attachments: метадані, вміст і виняток /download

З ресурсом attachments важливо втримати вже обрану межу: це підресурс задачі, а .../download потрібен не як бізнес-команда, а як окремий спосіб отримати бінарний вміст конкретного вкладення. Один шлях відповідає за метадані, інший — за вміст.

HTTP Шлях Зміст (людською мовою)
GET /api/v1/tasks/{taskId}/attachments Список метаданих вкладень задачі
POST /api/v1/tasks/{taskId}/attachments Завантажити вкладення до задачі
GET /api/v1/tasks/{taskId}/attachments/{attachmentId} Отримати метадані одного вкладення
GET /api/v1/tasks/{taskId}/attachments/{attachmentId}/download Отримати бінарний вміст вкладення
DELETE /api/v1/tasks/{taskId}/attachments/{attachmentId} Видалити вкладення (метадані + вміст)

Якщо вас дратує слово «метадані», це нормально. Але сенс тут дуже практичний: опис файлу, його ім’я, розмір, тип — це одне, а бінарний вміст — інше. Ми адресуємо їх різними ендпоінтами, тому що клієнту далеко не завжди потрібен сам файл у той самий момент, коли він читає список вкладень.

tags: lookup endpoint без окремого CRUD

Теги — підступний ресурс. Рука так і тягнеться зробити /tags, /tags/{tagId}, /tags/{tagId}/tasks і так далі. Але в нашому проєкті теги — це радше смислова частина задачі, ніж окрема сутність зі своїм великим життєвим циклом, а окремий ендпоінт для тегів потрібен як lookup: «покажи унікальні теги, які є в системі». Це зручно клієнту, але не роздуває домен.

HTTP Шлях Зміст (людською мовою)
GET /api/v1/tags Отримати список унікальних тегів

Це маленький ендпоінт, але він важливий своєю дисципліною: показує, що не все зобов’язане бути «повним CRUD». Іноді достатньо чесного допоміжного endpoint’а, який вирішує конкретну прикладну задачу.

4. Виключені шляхи

Канонічна карта корисна ще й тим, що одразу фіксує антиприклади. Скласти «включені» шляхи — це лише половина справи. Друга половина — чесно й явно записати, що ми не робимо. Це допомагає мозку не зісковзувати у звичні погані рішення, особливо коли з’являється бізнес-дія («треба завершити задачу!») і дуже хочеться написати /completeTask.

Антиприклади корисні ще й тим, що показують стиль, якого ми уникаємо. Тут ми не просто говоримо «погано», а одразу показуємо «як краще», щоб у вас у голові залишалася не заборона, а альтернатива.

Поганий варіант Чому це погано Канонічний варіант у нашому API
GET /api/v1/getTasks Дієслово в шляху, метод уже GET GET /api/v1/tasks
GET /api/v1/task Колекція в однині, незрозуміло: «одна чи список» GET /api/v1/tasks
POST /api/v1/completeTask RPC-команда замість роботи з ресурсом PATCH /api/v1/tasks/{taskId}
POST /api/v1/changeStatus Командний endpoint без ресурсу в адресі PATCH /api/v1/tasks/{taskId}
POST /api/v1/uploadFileToTask Команда «завантажити» замість підресурсу POST /api/v1/tasks/{taskId}/attachments
GET /tasks Випадає з єдиного базового шляху GET /api/v1/tasks
GET /api/v1/tasks/{taskId}/tags Сумнівна вкладеність: теги не живуть «під задачею» як окремий ресурс GET /api/v1/tags

Важливо вловити загальний принцип: ми не забороняємо дії, ми забороняємо перетворювати шлях на команду. Шлях — це адреса, а дія — це метод і, в деяких випадках, спосіб отримати потрібне представлення ресурсу.

5. Типові помилки під час складання endpoint-map

Проблема карти ендпоінтів не в тому, щоб її вигадати, а в тому, щоб утримати її чистою, коли починається серія «а давайте ще ось це швиденько додамо». Нижче — кілька типових помилок, які майже завжди трапляються в новачків. Це нормально: ви навчаєтеся, а не складаєте іспит із телепатії.

Помилка №1: змішувати «включені» та «експериментальні» шляхи в одній карті.
Часто з’являється бажання дописати «тимчасово для себе» другий шлях на кшталт /api/v1/getTasks, щоб «швидше перевірити». Проблема в тому, що тимчасове в API живе довше, ніж ваша мотивація. Через тиждень ви забудете, що це тимчасово, і отримаєте два публічні входи з однаковим змістом. Це ламає контракт і створює хаос.

Помилка №2: робити tags вкладеним ресурсом, бо «вони ж у задачі».
Так, теги належать задачі як список рядків, але це не означає, що для них потрібен endpoint /tasks/{taskId}/tags. У нашому проєкті теги — value-like частина задачі, а окремий endpoint потрібен як lookup по всій системі. Новачки часто плутають «поле в ресурсі» і «ресурс в API», через що з’являються зайві й дивні шляхи.

Помилка №3: намагатися виразити зміст через URI, забуваючи про HTTP-метод.
Іноді люди пишуть /api/v1/tasks/create, /api/v1/tasks/update, /api/v1/tasks/delete, бо «так зрозуміліше». Але це саме той випадок, коли ви будуєте протокол у протоколі: у вас уже є HTTP, і він уже вміє позначати операції методами. Якщо ви починаєте дублювати зміст методом і шляхом, карта стає довшою й менш передбачуваною.

Помилка №4: занадто глибока вкладеність «бо пов’язано».
Зв’язок сутностей у домені ще не означає, що шлях має перетворитися на роман у трьох томах. Якщо ви бачите щось на кшталт /tasks/{taskId}/comments/{commentId}/attachments/{attachmentId}, це зазвичай сигнал, що межі обрано невдало. У нашому проєкті вкладення прив’язане до задачі, тому воно живе в /tasks/{taskId}/attachments/..., і цього достатньо.

Помилка №5: забути про єдиний базовий шлях і почати «іноді так, іноді так».
Дуже легко за звичкою написати один endpoint як /tasks, другий як /api/v1/tasks, третій як /api/tasks. На рівні маленького проєкту це виглядає як «ну й що». На рівні контракту це означає, що API не має єдиної карти, і клієнти мають вгадувати, де який стиль. Рівний префікс /api/v1 — це проста дисципліна, яка сильно знижує шанс випадкового роздвоєння реальності.

1
Опитування
REST URI, рівень 4, лекція 4
Недоступний
REST URI
Ресурси та контракти API
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ