Comments 39
А где, собственно, про REST? Описывать рекомендации к оформлению эндпоинтов и работе с HTTP это хорошо, но REST не про протоколы.
а про что?
Большая часть статьи описывает проектирование ручек REST API. Можно ли назвать ручку API протоколом? ))) Более детально про REST (всевозможные глаголы, коды ошибок и т.д.) почитаешь в документации. Цель статьи - показать, что вырождение REST в RPC связано с непониманием сути REST, а не с какими-то особенностями или ограничениями архитектурного подхода.
APplication Interface
Это кто такую расшифровку API придумал? Нелепо же выглядит.
правильно - Application Programming Interface, подсказывает гугол в первой же выдаче )
Я то правильную знаю. Тут вопрос откуда автор черпал информацию. А то возникают сомнения в его компетентности и смысле читать статью. Там и так с самого начала полно воды.
Компетентному специалисту не составит труда не только подметить некоторые неточности с статье, но и в принципе оспорить главную мысль, причём аргументированно оспорить. Всегда интересно узнать опыт другого специалиста. Поэтому, если тебе хватает компетентности в этой теме, то можешь оспорить :)
Не вижу предмета для спора. При беглом взгляде не заметил ничего, кроме вариантов составления путей. Но это вкусовщина и зависит от конкретного проекта. По-моему, вы пытаетесь заново придумать то, что уже написано в книжках и реализовано в популярных фреймворках.
А здесь нет спора. Суть статьи в том, чтобы показать, что эта "вкусовщина" влияет на удобство поддержки и доработки API, а также оказывает влияние на архитектуру сервиса, что в свою очередь оказывает положительное влияние на масштабируемость, стабильность и поддержку сервиса в целом. Для облегчения понимания приведено множество примеров. Насчёт мелочей. Можно рассмотреть обычный понятный случай из жизни: жонглирование 4-мя мячами (взял побольше, чтобы наверняка). Кто умеет жонглировать таким количеством мячей? Наверное, далеко не каждый. А ведь можно разобрать какой мячик в какой момент времени и где должен находится. И тем не менее, если, после подробного инструктажа, попытается жонглировать новичок, то у него, скорее всего, ничего не получится. А если взять профессионального жонглёра, то у него всё получится. А почему? Да потому что мастер жонглирования "мелочится" - он делает те вещи, которые новичок в принципе не замечает или считает вкусовщиной. Так и в любом деле. Профессионал всегда мелочится, но умело... Ещё раз повторю, эта статья не для новичков. Здесь очень много мелочей :)
А вообще, одно дело знать правильную формулировку. Другое дело её понимать ) Если у тебя есть пример из твоей практики, который просто перечеркнёт смысл этой статьи, то буду признателен, если поделишься знанием + аргументированно докажешь, что твои подходы в проектировании лучше - объективно покажешь, какие проблемы твой подход решает, а какие добавляет, т.к. идеального решения не бывает - всегда какие-то вопросы решаются, а какие-то добавляются.
Моё изобретение ) Но сути не меняет - интерфейс приложения/сервиса
А зачем искажать уже общепринятые понятия? И как теперь быть уверенным, что вы ещё чего-то не нафантазировали в статье?
Если речь про API = APplication Interface, то признаю, моя ошибка. Она была связана со стереотипом, который давно сформировался. В этом моменте был уверен, поэтому не перепроверил + статья не о том, как точно расшифровать термин API. Всем понятно, что это интерфейс приложения. Конечно же, API = Application Programming Interface. Спасибо за замечание, в следующий раз постараюсь быть повнимательнее. Более того, я промахнулся, указав лёгкий уровень статьи, т.к. материал не для новичков. Его можно понять, имея определённый опыт в проектировании REST API. Насчёт фантазий...вся статья состоит из фантазий, это совершенно верное утверждение. Я бы даже уточнил: не фантазии, а личное восприятие проектирования автором статьи. Но нужно иметь в виду, что это личное восприятие опирается на личной опыт проектирования. В статье я делюсь личным опытом разработки REST API. Цель статьи - не показать читателям какой я классный, это я знаю и без них :) А обсудить с опытными разработчиками подходы к проектированию REST API, чтобы увидеть недочёты в своём личном восприятии и повысить качество разрабатываемых сервисов.
Я придумал. Свято верил, что это просто application interface. Но суть всё равно одна - интерфейс приложения
REST он как e-mail - простой, надежный, но устаревший и не отвечает современным требованиям. Но все им пользуются. Но нет четкого стандарта. И каждый добавляет в него что-то новое, согласно своей задаче, и он уже стал чем-то расплывчатым и непонятным.
В статье хотел показать, что расплывчивость и непонятность REST связана с непониманием сути REST. Это как ООП и процедурный стиль - наличие классов не гарантирует, что код написан в стиле ООП. Так и здесь, использование GET, POST, PUT и т.д. не гарантирует наличие REST API, можно получить RPC или что-то более оригинальное - в таких случаях говорят про REST-подобное API. Посыл статьи такой: прежде, чем делать какое-нибудь отступление от REST API, задумайся - может быть допустил ошибку в архитектуре сервиса? Может быть каких-то сущностей не хватает?
Наблюдая за похожими эволюциями систем, я заметил, что достаточно было прикрутить всего одну ручку
Ага, а как будет для нее выглядеть код на сервере? Будете проверять для каждого поля изменилось ли значение и делать роутинг вручную по этой информации? А другой программист потом должен в этом разбираться. Где же тут "проще поддерживать"?
Если вы делаете сущность с 4 CRUD-действиями, значит логика взаимодействия с ней переносится на клиента. Дальше оказывается, что пользователь может сам перевести заказ в оплаченные, или что если передать id в массиве contacts, то пользователь может прикрепить себе чужой email, и появляются всякие проверки для защиты. Дальше оказывается, что в бизнес-логике действий с сущностью больше чем 4, и начинаются попытки замапить 4 HTTP-действия на все бизнес-действия, в результате на сервере получается такой же RPC, только более запутанный. Если у вас 8 разных бизнес-действий с сущностью, то вам в любом случае надо все их реализовать, иначе программа будет работать не так, как нужно бизнесу. Вопрос только в том, как вы сделаете программный интерфейс для них.
Поэтому правильный подход это делать RPC с действиями, соответствующими бизнес-логике. Для каждого будут свои входные данные со своими правилами валидации. Для PATCH /users/1/activate
вообще не будет входного DTO, для PATCH /users/1/rename
будет {"name": "NewName"}
, для PATCH /users/1/new_email
будет {"email": "new_email@example.com"}
с валидацией что поле required и в формате email, и процедурой подтверждения нового email. Но только если бизнес действительно хочет сделать отдельные действия с одним полем ввода.
Тогда и не возникает вопроса "Как сделать эндпойнт для отправки email", просто берем и делаем.
Пока читал, аж прослезился...невольно вспомнилась старая песенка: "Какая боль! Какая боль! Аргентина-Ямайка 5:0" ))... Много слов. Честно говоря, я не понял суть проблемы )) Попробуем разобраться. В статье я обратил внимание, что вместо 10 ручек для crud-операций над сущностью, достаточно 4-х ручек. Обычно разработчики стараются следовать принципам чистой архитектуры, т.е. создавать слоённую архитектуру: middleware, контроллеры, бизнес-логика (кто-то называет сервисами, кто-то называет usecase - не суть), слой репозиториев. Допустим, у нас 10 оригинальных ручек для одной сущности. В силу непреодолимых обстоятельств (пожеланий бизнеса) требуется сущности добавить поле. Какие действия на бэке? - прикрутить новую оригинальную ручка, т.к. нужно чтить стиль "отцов-основателей". А это значит, для новой оригинальной ручки нужно написать логику в контроллере, в бизнес-логике, в репозитории, добавить её в роутинг, задуматься над правами. То есть при таком подходе при малейшем изменении сущности нужно проделывать хороший объём работы, вместо того, чтобы просто добавить новое свойство в сущность. Если нужна валидация, то её придётся писать, как при 10 ручках, так и при 4-х. Не думаю, что какой-нибудь разработчик возрадуется 10-ти ручкам вместо 4-х, так как при 10-ти ручках объём поддерживаемого кода больше, и, что самое печальное, при добавлении новых свойств сущности, он будет только увеличиваться...Если речь идёт "PUT или PATCH", так здесь нет однозначного ответа - зависит от контекста + выходит за рамки вопросов, обсуждаемых в статье.
В статье я обратил внимание, что вместо 10 ручек для crud-операций над сущностью, достаточно 4-х ручек.
А я обратил внимание, что это не так. Если вам надо 10 ручек, значит вам надо 10 ручек.
Если они находятся не в API, значит находятся где-то еще. Например на клиенте.
Если они не описаны в API явно, значит описаны неявно.
В силу непреодолимых обстоятельств (пожеланий бизнеса) требуется сущности добавить поле.
Какие действия на бэке? - прикрутить новую оригинальную ручка
Нет. Ни один принцип программирования или проектирования не предлагает делать отдельный эндпойнт для каждого поля. Это ерунда какая-то.
Действия на бэке - добавить поле в сущность и в DTO тех эндпойнтов, которые выполняют бизнес-действия, связанные с этим полем.
На всякий случай - запрос с одним полем PATCH /user/1 {"new_field": "value"}
не противоречит принципам REST.
То есть при таком подходе при малейшем изменении сущности нужно проделывать хороший объём работы
Поэтому нет, описанный вами объем работы проделывать не нужно. Никто так не делает.
Если нужна валидация, то её придётся писать, как при 10 ручках, так и при 4-х.
Ну так дело в том, что для разных действий ее проще написать.
Для одного действия поле является required, а для другого нет. Какое правило валидации делать для этого поля?
Вот у нас есть сущность Article. У нее 3 состояния - "В черновике", "На модерации", и "Опубликована".
Пока статья в черновике, текст и заголовок могут быть пустыми.
Для отправки на модерацию они должны быть заполнены.
Пока статья на модерации, автору менять текст и заголовок нельзя.
При этом модератор текст и заголовок менять может.
Опубликованную статью на модерацию отправлять нельзя.
При редактировании она должна автоматически скрываться в черновики, после этого отправка на модерацию становится доступной.
Покажите, как вы это сделаете с одним эндпойнтом PATCH?
С RPC это делается тривиально, и никаких сложностей не возникает. При этом для модератора вообще может быть своё API, недоступное снаружи.
А я обратил внимание, что это не так. Если вам надо 10 ручек, значит вам надо 10 ручек.Если они находятся не в API, значит находятся где-то еще. Например на клиенте.Если они не описаны в API явно, значит описаны неявно.
Если API требуется 10 ручке, то, конечно, нужно прикрутить 10 ручек. Но если речь про конкретную сущность, то это избыточность - для конкретной сущности необходимо и достаточно 4 ручек.
Нет. Ни один принцип программирования или проектирования не предлагает делать отдельный эндпойнт для каждого поля. Это ерунда какая-то.
Действия на бэке - добавить поле в сущность и в DTO тех эндпойнтов, которые выполняют бизнес-действия, связанные с этим полем.
На всякий случай - запрос с одним полем
PATCH /user/1 {"new_field": "value"}
не противоречит принципам REST.
Так я про это и пишу, что это ерунда какая-то. Для изменения состояния конкретной сущности требуется всего одна ручка - PUT или PATCH (что именно, зависит от контекста).
Поэтому нет, описанный вами объем работы проделывать не нужно. Никто так не делает.
Если учесть, что много где всю логику пишут прямо в контроллере, то, возможно, не придётся. Обычно такой код называют legacy. Его дорабатывают до определённого момента, потом либо разработчик с фразой "мне пора покорять новые вершины" увольняется, либо сервис переписывается с нуля. Паттерн, когда всё пишется в контроллере имеет право на существование, есть реально ситуации, когда он уместен. Но беда в том, что он не панацея, а им злоупотребляют.
Вот у нас есть сущность Article. У нее 3 состояния - "В черновике", "На модерации", и "Опубликована".Пока статья в черновике, текст и заголовок могут быть пустыми.Для отправки на модерацию они должны быть заполнены.Пока статья на модерации, автору менять текст и заголовок нельзя.При этом модератор текст и заголовок менять может.Опубликованную статью на модерацию отправлять нельзя.При редактировании она должна автоматически скрываться в черновики, после этого отправка на модерацию становится доступной.
Здесь можно обойтись одной ручкой PATCH или PUT (я бы выбрал PUT). Как я понял, разные поля могут принимать определённые значения (или в принципе иметь значение) в зависимости от значения поля status сущности. Можно написать валидатор, который будет следить за соблюдением этих правил. Более того, если используется postgres, то валидацию можно переложить на сторону БД - написать restriction. Но если валидация достаточно сложная, то, конечно, её лучше делать на стороне сервиса. С gRPC будет что-то похожее, а вот 100 500 ручек в стиле RPC я бы не прикручивал.
Беда темы проектирования REST API заключается в том, что просто почитать статью про REST недостаточно. Здесь требуются знания архитектуры сервисов, базы данных, с которой работаешь и т.д. (это то, что на поверхности). Задача для senior.
для конкретной сущности необходимо и достаточно 4 ручек.
Именно об этом я и говорю. Нет, недостаточно. Это можно сделать, но тогда код на клиенте и на сервере будет слишком запутанный, его будет сложнее поддерживать.
Так я про это и пишу, что это ерунда какая-то.
Вы это пишете, как будто конкретно я или подход RPC в целом рекомендует так делать. И пытаетесь этим доказать, что подход RPC неправильный. А на самом деле это вовсе не подход RPC.
а вот 100 500 ручек в стиле RPC я бы не прикручивал
"100 500 ручек" это не стиль RPC. В RPC число ручек зависит от количества бизнес-действий, а не от количества полей. И у вас они тоже будут, только вы будете их вызывать кодом вида
if ($prevStatus === DRAFT && $newStatus === ON_MODERATION) $this->sendOnModeration($article)
;
Если учесть, что много где всю логику пишут прямо в контроллере, то, возможно, не придётся.
Паттерн, когда всё пишется в контроллере имеет право на существование
При чем тут логика в контроллере? Я про нее не говорил и не подразумевал.
Можно написать валидатор, который будет следить за соблюдением этих правил.
Можно. Это и есть пример усложнения кода, о котором я говорю.
Другому программисту придется распутывать, какие правила к какому бизнес-действию относятся, потому что они все у вас будут в одном эндпойнте обновления.
если используется postgres, то валидацию можно переложить на сторону БД
Можно. Это тоже пример усложнения. С RPC это просто не нужно.
- Покажите, как вы это сделаете с одним эндпойнтом PATCH?
- Здесь можно обойтись одной ручкой
Ну так покажите, как она будет выглядеть, я же именно об этом и попросил. Почему вы вместо кода для демонстрации подхода предпочли написать большой коммент с рассказами, что это несложно?
С RPC у меня это заняло 130 строк и 20 минут, без всяких триггеров в БД.
Код
class Article {
int $id;
int $userId;
string $title;
string $text;
ArticleStatus $status;
}
enum ArticleStatus {
case DRAFT = 1;
case ON_MODERATION = 2;
case PUBLISHED = 3;
}
class SaveArticleDTO {
string $title;
string $text;
}
class ArticleUserController {
function save(string $id, SaveArticleDTO $dto) {
$article = $this->findEntity($id);
$validationErrors = $this->articleService->validateForSave($article);
if (!empty($validationErrors)) {
return $this->errorResponse($validationErrors);
}
$this->articleService->save($article, $dto);
return $this->successResponse($article);
}
function sendOnModeration(string $id) {
$article = $this->findEntity($id);
$validationErrors = $this->articleService->validateForSendOnModeration($article);
if (!empty($validationErrors)) {
return $this->errorResponse($validationErrors);
}
$this->articleService->sendOnModeration($article);
return $this->successResponse($article);
}
private function findEntity(string $id): Article {
$article = $this->articleRepository->findOne($id);
// автор может редактировать только свои статьи
if ($article === null || $article->userId !== $this->getCurrentUser()->id) {
throw new HttpNotFoundException('Article not found');
}
return $article;
}
}
class ArticleSerivce {
function validateForSave(Article $article) {
$errors = [];
if ($article->status === ArticleStatus::ON_MODERATION) {
$errors['status'] = 'Article is on moderation';
}
return $errors;
}
function save(Article $article, SaveArticleDTO $dto) {
$article->status = ArticleStatus::DRAFT;
$article->title = $dto->title;
$article->text = $dto->text;
$this->entityManager->save($article);
$this->entityManager->flush();
}
function validateForModeration(Article $article) {
$errors = [];
if ($article->status !== ArticleStatus::IN_DRAFT) {
$errors['status'] = 'Article must be in draft';
}
if (empty($article->title)) {
$errors['title'] = 'Title must not be empty';
}
if (empty($article->text)) {
$errors['text'] = 'Text must not be empty';
}
return $errors;
}
function sendOnModeration(Article $article) {
$article->status = ArticleStatus::ON_MODERATION;
$this->entityManager->save($article);
$this->entityManager->flush();
}
function publish(Article $article) {
$article->status = ArticleStatus::PUBLISHED;
$this->entityManager->save($article);
$this->entityManager->flush();
}
}
class ArticleModeratorController {
function save(string $id, SaveArticleDTO $dto) {
$article = $this->findEntity($id);
$this->articleService->save($article, $dto);
return $this->successResponse($article);
}
function publish(string $id) {
$article = $this->findEntity($id);
$this->articleService->publish($article);
return $this->successResponse($article);
}
private function findEntity(string $id): Article {
$article = $this->articleRepository->findOne($id);
// модератор может редактировать любые статьи
if ($article === null) {
throw new HttpNotFoundException('Article not found');
}
return $article;
}
}
Именно об этом я и говорю. Нет, недостаточно. Это можно сделать, но тогда код на клиенте и на сервере будет слишком запутанный, его будет сложнее поддерживать.
Совершенно верно! Логика станет слишком запутанной. Но эта ситуация будет, если систему проектировать монолитом. Т.е. есть некоторый бэк, у которого есть ручки. Каждая ручка изменяет состояние сущности и запускает какую-то логику. Но в своей статье я подсветил этот момент, что если создавать REST API, то придётся и архитектуру переосмыслить. В своей статье я пытался донести, что REST API - это просто получение и изменение состояний конкретных сущностей: получение списка пользователей, получение конкретного пользователя, изменение конкретного пользователя, удаление конкретного пользователя. Более того, как раз-таки специально рассмотрел, казалось бы, тупиковый случай - отправка письма. Ведь это же чистое действие. О какой сущности здесь может быть речь? Тем не менее, и такие случае можно обыграть через REST API - создаётся сущность task (задача отправить письмо). Также я подсвечивал, что непосредственной отправкой письма API не занимается. Письмо отправляет фоновый процесс. Сервис получается состоящим из двух независимых модулей: API, джоб для отправки письма. Более того, каждый модуль должен запускаться в отдельном контейнере. Далее в статье привёл какие плюсы даёт эта "сложная архитектура".
Я наблюдаю либо неготовность понять материал, либо невнимательное чтение. Если невнимательно читаете, то зачем задавать вопросы? - возможно, ответ уже есть в материале статьи. Если испытываете трудности с пониманием такой архитектуры, то я рекомендую сначала познакомиться с DDD, SOLID и т.д.
Ну так покажите, как она будет выглядеть, я же именно об этом и попросил. Почему вы вместо кода для демонстрации подхода предпочли написать большой коммент с рассказами, что это несложно?
А что здесь описывать? Есть ручка PUT, при помощи которой можно менять состояние article, т.е. менять поля status, title. В запросе отправляется состояние article с изменённым значением title. Либо на стороне сервиса, либо на стороне БД (postgres) запускается логика проверки возможности изменить title в зависимости от status. Если что-то можно изменить в статье, то статус автоматом возвращается в "draft". По-моему всё просто :)
Вы это пишете, как будто конкретно я или подход RPC в целом рекомендует так делать. И пытаетесь этим доказать, что подход RPC неправильный. А на самом деле это вовсе не подход RPC.
Эта статья не про RPC. Она о том, что, если не получается прикрутить ручку в стиле REST, то это не проблема REST, это проблема архитектуры конкретного сервиса: монолит, не хватает какой-то сущности и т.д. Ни в коем случае я не отрицаю RPC, я лишь обращаю внимание, что REST реален. Он есть!...в умелых руках ))
Тем не менее, и такие случае можно обыграть через REST API - создаётся сущность task
Никто не говорит, что нельзя. Разговор о том, что это усложняет усложняет поддержку, а не упрощает, как вы говорите.
В бизнес-требованиях нет такой сущности, а в коде есть. Поэтому другим программистам приходится разбираться, зачем она нужна.
Я наблюдаю либо неготовность понять материал, либо невнимательное чтение.
Ну раз вы делаете вид что не поняли, напишу прямо. Я говорю о том, что REST это неправильный подход, и использовать его вообще не надо.
И что ваше утверждение о том, что он упрощает поддержку, полностью неверно.
А что здесь описывать?
Код. Не описывать, а написать. Вы же сказали, что его будет проще поддерживать, а подтвердить свои слова примером отказываетесь.
В запросе отправляется состояние article с изменённым значением title.
запускается логика проверки возможности изменить title в зависимости от status.
Если что-то можно изменить в статье, то статус автоматом возвращается в "draft".
По-моему всё просто
Это всё верно и для RPC. Получается, и с RPC тоже всё просто?
А если так, зачем тогда нужен REST со всеми этими дополнительными сущностями и триггерами в базе?
Эта статья не про RPC.
В статье вы обсуждаете вопрос "как обыграть ситуацию, когда нужна ручка просто для отправки уведомления на почту?". Это в чистом виде RPC, "отправить уведомление" это название процедуры.
Также вы обсуждаете ситуацию "бизнес просит добавить ручку активации пользователя". URL вида /users/1/activate
это и есть RPC.
Вы сказали, что надо все такие действия с пользователем совместить в одно, и что это будет проще в поддержке. Поэтому ваша статья именно о том, что REST лучше RPC.
А я объяснил, что это не так. Что вы игнорируете, что для всех этих функций программа должна делать разные действия. Для изменения имени надо только поменять данные в базе, для активации надо также отправить письмо "Ваш аккаунт активирован", для подключения нового email отправить письмо "Подтвердите email" с токеном восстановления, и сохранить этот токен в базу. А вы не рассматриваете сложность кода после такого совмещения.
Никто не говорит, что нельзя. Разговор о том, что это усложняет усложняет поддержку, а не упрощает, как вы говорите.В бизнес-требованиях нет такой сущности, а в коде есть. Поэтому другим программистам приходится разбираться, зачем она нужна.
Похоже мы общаемся на разных языках )) Кажется, проблема в умении писать хороший код. В незнании, что классу не обязательно самому выполнять всю логику, часть логики он может делегировать другому классу - грамотная композиция позволяет избежать написания "каши".
Ну раз вы делаете вид что не поняли, напишу прямо. Я говорю о том, что REST это неправильный подход, и использовать его вообще не надо.И что ваше утверждение о том, что он упрощает поддержку, полностью неверно.
Когда человек разбирается с какой-то темой, он ничего не знает. В такой ситуации он принимает на веру первое попавшееся ему решение вопроса. Обычно самое простое. Другие решения они, на этом этапе своего развития, начинают категорически отрицать. Например, когда человек делает первые шаги в программировании, он пишет код в процедурном стиле. ООП ему кажется чем-то сложным и бессмысленным. Когда человек знакомится с межсервисным взаимодействием, то оно сводится к RPC, как к достаточно простому, т.к. не требует глубокого понимания архитектурных подходов при проектировании сервисов. Потом, когда человек получит достаточно знаний, он начинает понимать, что нет "хороших" и "плохих" решений. Просто каждое решение актуально для конкретного случая. В своей статье я отметил, что REST API требует серьёзной проработки архитектуры сервиса...Так что REST API - хорошая вещь, просто нужно "уметь готовить".
Код. Не описывать, а написать. Вы же сказали, что его будет проще поддерживать, а подтвердить свои слова примером отказываетесь.
Я описал максимально подробно. Не должно быть трудностей с реализацией.
Это всё верно и для RPC. Получается, и с RPC тоже всё просто?А если так, зачем тогда нужен REST со всеми этими дополнительными сущностями и триггерами в базе?
В RPC тоже можно сделать одну ручку: users/update. Но зачем RPC, когда есть gRPC? ) Кстати, про триггеры я не писал, restrictions в pg - это не триггеры. Зачем REST? - один из архитектурных подходов )
В статье вы обсуждаете вопрос "как обыграть ситуацию, когда нужна ручка просто для отправки уведомления на почту?". Это в чистом виде RPC, "отправить уведомление" это название процедуры.
Также вы обсуждаете ситуацию "бизнес просит добавить ручку активации пользователя". URL вида/users/1/activate
это и есть RPC.Вы сказали, что надо все такие действия с пользователем совместить в одно, и что это будет проще в поддержке. Поэтому ваша статья именно о том, что REST лучше RPC.
А я объяснил, что это не так. Что вы игнорируете, что для всех этих функций программа должна делать разные действия. Для изменения имени надо только поменять данные в базе, для активации надо также отправить письмо "Ваш аккаунт активирован", для подключения нового email отправить письмо "Подтвердите email" с токеном восстановления, и сохранить этот токен в базу. А вы не рассматриваете сложность кода после такого совмещения.
В статье я показал, как можно кейс, который обычно обыгрывается в стиле RPC, обыграть в стиле REST
В своей статье я не говорю, что REST круче RPC, а лишь показал, что этот архитектурный подход вполне применим на практике, какие плюсы он даёт. И даже минус подсветил - требуется определённая квалификация разработчика.
А вот ярые сторонники RPC, могут тоже написать статью про RPC. Где объективно подсветят плюсы и минусы на конкретных примерах.
В незнании, что классу не обязательно самому выполнять всю логику, часть логики он может делегировать другому классу
Похоже, вы не понимаете, о чем идет речь, и не хотите понять. Либо у вас вообще нет опыта в программировании.
При чем тут другие классы? Я говорю именно про тот код, который будет выполнять делегирование. Напишите конкретный код для примера, который я привел, я вам покажу пальцем, где у вас будет "каша".
В своей статье я отметил, что REST API требует серьёзной проработки архитектуры сервиса
Это еще один пример, почему поддержка будет сложнее, а не проще.
Но зачем RPC, когда есть gRPC? )
В этом вопросе отсутствует смысл. Выглядит так, что у вас нет опыта в программировании, и вы не понимаете, о чем говорите.
а лишь показал, что этот архитектурный подход вполне применим на практике, какие плюсы он даёт
Ок, объясню еще раз. Ваше заявление о плюсах - это ложь. Потому что вы игнорируете некоторые важные последствия такого решения, которые не являются плюсами.
Я описал максимально подробно. Не должно быть трудностей с реализацией.
Почему вы уходите от ответа и подменяете понятия? Я разве где-то сказал, что будут трудности с реализацией? Я сказал, что код у такой реализации будет сложнее, поэтому его будет сложнее поддерживать.
Ладно, напишу за вас, раз вы сами неспособны.
class ArticleDto {
public string $text;
public string $title;
public ArticleStatus $status;
}
enum ArticleStatus {
case DRAFT = 1;
case ON_MODERATION = 2;
case PUBLISHED = 3;
}
class ArticleController {
function update(string $id, ArticleDto $newArticleState) {
$article = $this->findEntity($id);
// усложнение 1
try {
$this->articleService->update($article, $newArticleState, $this->getCurrentUser());
return $this->successResponse($article);
} catch (ValidationException $exception) {
return $this->errorResponse($exception->getErrors());
}
}
private function findEntity(string $id): Article {
$article = $this->articleRepository->findOne($id);
$currentUser = $this->getCurrentUser();
// усложнение 2
$canEdit = $currentUser->id === $article->user_id || $currentUser->getRole() === Role::MODERATOR;
if ($article === null || !$canEdit) {
throw new HttpNotFoundException('Article not found');
}
return $article;
}
}
class ArticleService {
function update(Article $article, ArticleDto $newArticleState, User $currentUser) {
// усложнение 3
if ($article->status === ArticleStatus::DRAFT) {
if ($newArticleState->status === ArticleStatus::DRAFT) {
$this->save($article, $newArticleState);
} else if ($newArticleState->status === ArticleStatus::ON_MODERATION) {
$this->save($article, $newArticleState);
$errors = $this->validateForSendOnModeration($article);
if (!empty($errors))
throw new ValidationException($errors);
$this->sendOnModeration($article);
} else if ($newArticleState->status === ArticleStatus::PUBLISHED) {
throw new ValidationException(['status' => 'This change is not allowed']);
}
} else if ($article->status === ArticleStatus::ON_MODERATION) {
if ($currentUser->getRole() !== Role::MODERATOR) {
throw new ValidationException(['status' => 'This change is not allowed']);
}
$this->save($article, $newArticleState);
if ($newArticleState->status === ArticleStatus::PUBLISHED) {
$this->publish($article);
}
} else if ($article->status === ArticleStatus::PUBLISHED) {
if ($newArticleState->status !== ArticleStatus::DRAFT) {
throw new ValidationException(['status' => 'This change is not allowed']);
}
$this->save($article, $newArticleState);
}
}
// делегирование
function save(Article $article, ArticleDto $newArticleState) { ... }
function validateForSendOnModeration(Article $article) { ... }
function sendOnModeration(Article $article) { ... }
function publish(Article $article) { ... }
}
Вот "усложнение 3" это и есть ваша "каша". И вы никак ее не уберете, разве что можете немного трансформировать. Вопрос на засыпку - сколько времени у вас займет, чтобы определить, работает ли этот код в соответствии с указанными выше требованиями, или в нем есть ошибка?)
Зачем здесь код валидатора? Какое отношение валидатор имеет к API? :) А что, если бы речь шла про джоб? У джоба нет API. Более того, API может иметь валидацию под капотом, а может не иметь :) Вообще говоря, я всегда думал, что конкретный сервис инкапсулирует определённую логику. Знаете, что такое инкапсуляция? - Думаю, нет :) Инкапсуляция - это сокрытие деталей. Предположим, есть некоторый сервис; у него есть API. Как с ним происходит взаимодействие? Используя его интерфейс, на вход подаётся A, а он отвечает нам B. При этом, что там происходило внутри, нас не особо касается - это внутренняя кухня сервиса. Валидация относится к внутренней кухне конкретного сервиса, то есть к его деталям. Интерфейс все эти детали скрывает. Более того, конкретные детали не должны оказывать влияние на интерфейс. Это связано с тем, что детали изменчивы, а интерфейс не должен меняться.
Я ещё раз обращаю внимание, что наблюдаются явные проблемы с проектированием сервисов. Сервис - это не только API. Я считаю, что прежде, чем писать какие-то глупости: код, который совершенно не относится к теме; обвинения, что какие-то архитектурные подходы отрицаются - хотя этого нет; предположения или утверждения, что кто-то не имеет писать код и т.д.; следует научиться "не мешать мух с котлетами". Я рекомендую упорядочить тот минимальный объём знаний, который имеется, и интенсивно получать новые знания - читать книжки. Кажется, сейчас у вас тот самый уровень, когда нужно больше слушать, чем говорить.
Знаете, что такое инкапсуляция? - Думаю, нет
Продолжаете переход на личности? Я его проигнорировал в предыдущих комментариях, предполагая, что вы достаточно умны, чтобы самому его прекратить, но видимо я ошибся.
Думаете, вы так выглядите умнее для людей, читающих эту дискуссию? Это не так, уровень вашей квалификации всем заметен.
Более того, конкретные детали не должны оказывать влияние на интерфейс.
В данном случае происходит наоборот, интерфейс оказывает влияние на детали. Потому что детали получают данные для обработки от этого интерфейса. Жаль, что вы этого не понимаете.
При этом, что там происходило внутри, нас не особо касается - это внутренняя кухня сервиса.
Если вы делаете утверждение "это облегчает доработку и поддержку API", то именно что касается. Потому что это утверждение про внутреннюю кухню сервиса.
Зачем здесь код валидатора? Какое отношение валидатор имеет к API?
Такое, что "написать API" как раз и означает означает "написать этот код", и "поддержка", про которую вы говорите, это поддержка именно этого кода.
Понятно все с вами, у вас нет опыта, дискуссия не имеет смысла.
Извиняюсь за "некропостинг". Почитал несколько веток ваших комментариев в разных темах насчет спора про REST и я в восторге. Пока что так и не увидел того, кто на деле бы, через код, показал вот эту вот мнимую выгоду именно RESTful api.
Сколько бы я не пытался найти в статьях и комментариях плюсы и реально правильное использование RESTful в бизнес логике, его тупо нет. Все статьи описывают плюсы только в самом простейшем CRUD и все. Там это реально выглядит красиво и удобно. Но как только бизнес логика становится чуть сложнее чем просто создать/обновить объект, то все начинает ссыпаться.
Я пока не наткнулся на ваши комментарии, все это время думал, что может я туплю и что-то не понимаю с этим restful. Потому что если я начинаю разрабатывать какую-то реальную бизнес логику по restful, я понимаю, что в коде начинается какая-то каша с тучей проверок на каждое переданное поле в запросе и какой роли что и когда можно изменять.
И причем никто на реальном кейсе(даже вашем) не может продемонстрировать какую пользу принесет RESTful. Если даже и пытаются, то я вижу только как начинают появляться какие-то нагромождения if else в которых начинаешь теряться.
Смогли ли вы наконец-то понять RESTful или нашли какой-то хороший, настоящий пример с реализацией какой-нибудь бизнес логики на нем?
Спасибо. REST в виде CRUD использовать не надо, потому что если у нас CRUD на бэкенде, значит логика на фронтенде и контролируется клиентом, а это не то, чего мы хотим. А если добавлять защиту на бэкенде, то появляются усложнения, потому что CRUD-операции не содержат контекст, что пользователь хочет сделать. REST удобно использовать в виде процедур с параметрами, тогда он становится просто протоколом вызова.
У меня есть вот эта статья, можете посмотреть там примеры. Там не CRUD, а именно бизнес-действия, которые совершает пользователь, просто по бизнес-требованиям некоторые действия похожи на CRUD. Отличие в том, что им на вход приходит не сущность со всеми полями, как в примерах по REST, а DTO с параметрами действия. Например для создания товара нужно только имя, остальные поля заполняются пользователем позже при редактировании.
Все ничего только ссылок не хватает, а в частности
1) https://habr.com/ru/articles/739808/ [HTTP API & REST] Терминология. Мифология REST. Составляющие HTTP-запроса
2) Стандартизации REST как таковой нет, ну вот с разбегу RFCnnnn которое прямо описывало понятие таковое, не двусмысленно и четко - я не нашел
А соответственно, REST каждый волен понимать по своему, хотя есть разные трактовки на вкус и цвет
Такой, м. быть странный вопрос: что в данном контексте означает слово "ручка"? Как я понял, слегка погуглив, это некое сленговое выражение у российских программистов, причём (ну, я, всё-таки, не первую статью на Хабре читаю) не шибко широко употребляемое, и в разных ситуациях означающее разные вещи... Может быть, если автор пытается что-то объяснить, не стоит использовать необщеупотребимый профессиональный сленг, а стараться в каждом случае применять именно те термины или названия, которые скрываются под этим выражением? Мне непонятна эта статья именно из-за непрерывного упоминания таинственных "ручек"... Мне было бы интересно почитать, что такое REST API, поскольку мне до сих пор непонятно, зачем, казалось бы, универсальный сервисный подход "насаживать" на жёсткий синтаксис из нескольких глаголов. Тем более, что там, в принципе, одним POSTом можно делать всё - разве что GET для некоторого удобства оставить... Что делает REST RESTом? Только искусственный шаблон?
Ручка API - это метод API. Насчёт ручек...во всех командах, с которыми я работал, методы API всегда назывались "ручками". Причём, когда я впервые столкнулся с этим термином, мне сразу было понятно, что речь про методы API. А про что ещё может быть?
В сотый раз повторю, что цель статьи не "обмазать грязью" какие-либо архитектурные подходы, а всего лишь показать, что REST API вполне жизнеспособная вещь + если подойти к проектированию API на полном серьёзе, то могут прийти мысли по улучшению архитектуры сервиса.
Конечно, можно все запросы отправлять через POST. Я лично сталкивался с такими системами. Что я могу сказать про них? - они работают...как могут работают. Я даже сталкивался с системами, которые в любой ситуации возвращали 200-ый код ошибки. Немножко неудобны в поддержке - всегда нужно разбираться с логом. Тем не менее, они тоже выполняли свои функции...как могли, конечно.
Дело в том, что не все системы имеют REST API. Вот даже, если рассмотреть системы, которые принимают только POST запросы - это серьёзное отступление от принципов REST API, такие API называют по-другому. Чтобы не обидеть чувства разработчиков таких API, их называют REST-подобными API :) Более того, можно в принципе отказаться от REST API, RPC, gRPC, и даже REST-подобное API можно не проектировать...В одной довольно крупной и известной IT компании тимлид одной из команд придумал поистине "эффективное" решение - он осуществлял взаимодействие сервисов не через интерфейсы, а подключал код одного сервиса к другому, как библиотеку. Пачка сервисов получилась связанной на уровне кода. К сожалению, нет возможности описать всю боль такой "эффективной" разработки в комментарии - нужно написать отдельную статью.
А вообще
Мне было бы интересно почитать, что такое REST API, поскольку мне до сих пор непонятно, зачем, казалось бы, универсальный сервисный подход "насаживать" на жёсткий синтаксис из нескольких глаголов. Тем более, что там, в принципе, одним POSTом можно делать всё - разве что GET для некоторого удобства оставить...
эта статья для разработчиков, которые знают REST API, насмотрелись на реализации REST-подобных API и наслушались баек, почему подходы REST API нельзя было применить в конкретной ситуации; также требуется понимание "что такое хорошо, а что такое плохо" в проектировании архитектуры сервисов.
Что делает REST RESTом? Только искусственный шаблон?
Об этом эта статья.
В ответе используются коды ошибок: 200, 301, 401, 404, 500 и т.д
Скорее не коды ошибок, а коды ответов. Я думаю, неправильно называть 2хх кодом ошибки.
REST API и архитектура сервиса