Pull to refresh

Comments 41

Вы немного запутались в аббревиатурах. Гляньте этот материал, для распутывания.

Там написано, что GraphQL является примером RPC протокола
В этом я запутался?

GraphQL это язык

Язык, который определяет структуру и семантику данных, не может быть протоколом передачи данных, которому все равно, что внутри этих данных и как они устроены, покуда они в нужном (JSON) формате

Простите, читать "манюал" в котором смешивается красное и солёное не вижу смысла.

Есть очень тонкая грань между наивным заблуждением и беспросветной глупостью. Вы её только что пересекли. Помедитируйте над этим.

Взялись поучать, а в итоге пассивная агрессия и хамство. Класс.

Это его фирменный стиль :D

Так человек не видит смысла чему-то учиться.

"Чему-то" или "тому, чему учат конкретно здесь конкретным, выглядящим неприемлемо, методом"?

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

1) Даже если не видит, то что? 2) Вам ли оценивать других?

1) Ничего, а что? 2) А кому, если не мне?

Было: роутинг (URI, method) → (controller, function) и "бОльшая часть кода" в контроллерах, состоящая обычно из чтения параметров, валидации, вызова модели и форматирования ответа. Стало: роутинг RPC name → (class, function), чтение параметров унифицировано, валидация переехала из контроллеров в классы, форматирование ответов унифицировано. Шило на мыло. Унификацию может и должен обеспечивать фреймворк. В реальном приложении вряд ли удастся забыть про HTTP окончательно: как-то надо управлять кэшированием, принимать и отдавать BLOB'ы и файлы (в параллель с JSON-RPC, видимо). От JSON-RPC остались только Request и Response в качестве соглашения о структуре конкретного API, и то, ни "jsonrpc", ни "id" не используются. В документации обычно есть такой вводный раздел: "при ошибках мы возвращаем JSON с тектом в поле error..."


А насколько проще тестировать свои классы, а не контроллеры, насколько проще строить архитектуру приложения без ограничений фреймворка, его контекста, потока исполнения и магии.

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

Вы сделали шаг в правильном направлении с позиций MVC — вынесли код из контроллеров в модель ("свои классы"). Осталось разобраться с тем, что при их взаимных вызовах будет дергаться валидация. Когда вы разделите свои классы на "повторно успользуемую бизнес-логику" и "валидацию плюс вызов бизнес-логики", они станут правильными контроллерами, а "универсальный контроллер JSON-RPC" — адаптером самодельного роутинга к фреймворку.

Насчет "было-стало" - да, была передача данных, стала передача данных. JSON-RPC придаёт формат и строгость этой передаче. Мне эта дисциплина понравилась Тут персонально - пока не попробуешь, не поймешь нравится или нет

Управляемое кэширование на клиенте делаю сервис-воркером, на сервере - файловая система, Memcached, или что угодно - к HTTP транспорту не имеет отношения.

Принимать и отдавать файлы тоже можно массивами данных при желании, но это по усмотрению.

Но, главное, это вывод контроллеров MVC приложения из области действия HTTP контроллеров фреймворка - это уже очень много.

Да, всё это можно сделать и без JSON-RPC, но он помогает это увидеть и отвязать транспортный протокол от бизнес логики в голове разработчика.

В следующей статье покажу, как удобны batch пакеты.

Управляемое кэширование на клиенте делаю сервис-воркером, на сервере — файловая система, Memcached, или что угодно — к HTTP транспорту не имеет отношения.

Я имею в виду заголовки HTTP, которые нужны для кэширования и conditional GET: Cache-Control, ETag и другие. Ведь ответы на POST, используемый для RPC endpoint, по умолчанию не кэшируемые. Клиентом API может быть не только фронтэнд, но и другой сервис или кэширующий reverse proxy.


Принимать и отдавать файлы тоже можно массивами данных при желании, но это по усмотрению.

Как base64 params/result? Неэффективно для сети и для процессора на обеих сторонах. Так как у вас запрос-ответ RPC выполняются в одном HTTP-запросе, можно через Content-Disposition: attachment. Даже поддержка в контроллере не нужна, $_FILES уже есть (не видел PHP десять тысяч лет).

Если рассматривать целевую систему, то ИМХО должно быть некое отдельное приложение (да, конечно всегда все можно совместить в одно при большом желании), которое оперирует файлами и\или блобами (загружает во временное хранилище и отдает ссылку клиенту), а клиент в json-rpc уже передает ссылку на ресурс (или его идентификатор)

Я критикую подход из статьи, а не JSON-RPC как таковой. Автор пишет, что так как на REST органично ложится "не более трети" API web-приложения (то есть в приязке к HTTP), то удобнее все свалить в один эндпоинт с диспетчеризацией по RPC name, и это позволяет "отвязать транспортный протокол от бизнес-логики". Оказывается, что не всегда можно отвязать, либо надо структурировать (делить на два) API, исходя из протокола, а не бизнес-логики.


Например, JSON-RPC хорошо подходил для общения майнеров с пулами: простой формат, двусторонний поток сообщений (нужно для событий), асинхронные ответы (нужно для долгих операций), в качестве транспорта достаточно было обычного сокета.

не, здесь я лишь предложил, как бы (по моему мнению) было бы правильно реализовывать загрузку блобов в парадигме, когда у вас вся экосистема приложения построена вокруг json-rpc

Оказывается, что не всегда можно отвязать, либо надо структурировать (делить на два) API, исходя из протокола, а не бизнес-логики.

Звучит таки как критика протокола (json-rpc)? Потому что для фронта использовать json-rpc действительно не самая хорошая затея, например, и с этим сложно спорить, там куча аргументов против.

В статье именно использовать JSON-RPC для фронта и предлагается (в специфическом виде). Ваше решение годное, без претензий сделать на JSON-RPC то, для чего он не предназначен, просто оно за рамками подхода из статьи заменить весь REST на JSON-RPC.

Вот я согласен с kozlyuk, мне кажется, что вы, фигурально выражаясь, вместо того чтобы прибраться в доме, сносите его и стоите заново. Сразу скажу — я могу ошибаться, но вот складывается именно такое ощущение. Что контроллеры были толстоваты, но вместо похудания вы выбрали вот такой радикальный вариант.

Я не сношу дом. Я переношу код контроллера в другое место, убираю "extends ResourceController", и переписываю получение параметров в начале методов не из GET и POST, а из params. Постепенная легкая миграция.

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

Понятно.
Ну в общем, для вас внедрение JSON-RPC оказалось тем, чем для многих разработчиков оказывается внедрение шаблонизатора, который заставил их быть более дисциплинированными и не писать лишний код в шаблоне :)

В реальном приложении вряд ли удастся забыть про HTTP окончательно: как-то надо управлять кэшированием, принимать и отдавать BLOB'ы и файлы (в параллель с JSON-RPC, видимо)

Если мы говорим про json-rpc приложение, то строго говоря стоит забыть, потому что json-rpc это по определению transport agnostic протокол.

https://www.jsonrpc.org/specification#overview

JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. Primarily this specification defines several data structures and the rules around their processing. It is transport agnostic in that the concepts can be used within the same process, over sockets, over http, or in many various message passing environments

Как только вы завязываетесь в своем протоколе на http, половина того, что напридумана в json-rpc становится совершенно избыточной, а вторая половина - неудобной. Становится гораздо проще взять кастомный json-based протокол и описать его в openapi и реализовывать все стандартными инструментами для чего есть куча готовых библиотек.

От JSON-RPC остались только Request и Response в качестве соглашения о структуре конкретного API, и то, ни "jsonrpc", ни "id" не используются.

Работал в паре компаний где в качестве базового межсервисного протокола (еще задолго до моего прихода) был выбран json-rpc, ни в одной из них правильно его приготовить не смогли, в итоге везде получалось примерно это же самое, на что вы сетуете, оставался некий +- фиксированный конверт и "контракт", который можно было сделать гораздо удобней

Не сравнивали с GraphQL в деталях? Матерые библиотеки для фреймворков, подсветка и навигация в IDE, кеширование, запрос бинарных данных, аутентификация, типизация, ее совместимость с типами php?

Вопрос не мне, но отвечу. GraphQL предлагает больше абстракций: это запросы, изменения и подписки. Плюс ко всему это язык, который нужно парсить. У GraphQL всегда проблема с глубоко вложенными данными, а также проблема N+1, когда первый запрос достает сущности первого порядка, а затем для каждой из них делается запрос на родительские. В целом, я был удивлен тому, сколько обвесов нужно писать вокруг GraphQL, чтобы он работал как надо.

Я не работал с GraphQL. С моей точки зрения он и чистый REST нужны только для каких-то особых случаев - типа public API или сайты со всей логикой на фронте (типа тех что на Firebase). Фокус группа статьи - обычные вебсайты с бэкенд API. Когда пишешь веб или мобильное приложение где бэк заточен под фронт и наоборот, то вся эта свобода в выборе параметров запроса и ответа на клиенте не нужна и даже вредна.

К тому же, с моей точки зрения, архитектурно это вещи разного уровня. GraphQL это язык, который можно использовать поверх протокола JSON-RPC. У меня был программист толковый, который без согласования реализовал такой свой язык запросов на бэк на проекте, с указанием возврата связей по foreign_id и тому подобное. Эта абстракция оказалась очень неудобной и в разработке, и в отладке и вообще не нужной. 70%++ запросов на бэк - кастомные, когда при обработке нужно проделать определенные действия, не укладывающиеся в "просто запрос данных". Какие-то запросы проще выполняются исполнением одного специфичного SQL, а не нескольких простых запросов с промежуточной обработкой данных в коде, - потому что SQL это не только select/ update, join и where на которых идеологически основываются GraphQL, REST и AR/ORM библиотеки. Я уж не говорю о безопасности - давать возможность фронту самому решать, что он может запросить, чревато.

Единственная возможная вариация, это когда какие-то данные: например, users.transactions, запрашиваются с вебсайта и, скажем, с админки. Тогда надо выдавать разный набор, который определяется источником запроса и/или ролью пользователя (admin/manager/user). Но опять же, это должен решать бэк, в целях безопасности.

Вот честно, лично я не вижу большой разницы между стандартным контроллером и вашими method и params. Ну ведь то же самое все. params надо валидировать точно так же, как квери стринг при гет запросе, не говоря уже про REST, где те же самые параметры в том же самом джейсоне.


Я бы сказал, в статье есть некоторые признаки религиозного благоговения перед инстурментом. Очень показательна в этом плане история с роутингом. Вы взяли, и написали свой роутинг на коленке. И подаете это как достоинство — мол, при смене фреймворка надо будет только кинуть другой бридж с встроенного роутинга на самопальный! Но на самом деле это решение, прямо скажем, очень спорное. Надо объяснять — почему?


Кроме того, вы выкинули такую важную функцию контроллера, как коммуникация с клиентом. Вот у вас совсем вырожденный контроллер, который не то что тонкий, а вообще один на всех. Но если в одном месте убудет, то в другом месте обязательно прибудет. И прибыло у вас, в итоге — в модели. Фактически, клиент у вас лезет напрямую в модель. Но разделение ответственности между контроллером и моделью было введено не просто так. В итоге вы перегружаете уже модель, ненужными валидациями и сообщениями об ошибках.

Не знаю, что вы хотели сказать этой ссылкой, но большинство современных фреймворков дают этот паттерн из коробки без всяких json-rpc. Фактически в них json-rpc - это второй уровень роутинга (сначала по http пути, а потом уже телу запроса), что только усложняет работу

Ну тогда уж не второй, а пятый уровень "роутинга"

Сперва идет единая точка входа у DNS имени сайта

Потом IP адрес сервера

Потом порт, на котором крутится вебсервер и который использует URL как параметр

Затем точка входа бэкенд фреймворка

И потом "application level" - JSON-RPC

приложение обо всем этом уже обычно не знает

а так, че ж вы дальше не продолжили. есть же еще MAC адреса, роутинг по AS\BGP и прочие приколы на других уровнях

Замечу, что после JSON RPC к обычному REST уже не хочется возвращаться. В REST данные размазаны по всему запросу: метод, заголовки, параметры пути, query-параметры, тело JSON, тело multipart/form-data и так далее. Весь этот зоопарк утомляет.

JSON-RPC есть желание от любителей хранить всё в одном месте. По сути, это один единственный урл а-ля вебхука, принимающий запрос и уже под капотом разруливающий вызов того или иного кода.

С точки зрения разработки это сразу превратится в кашу т.к. нужно будет самим вручную добавлять управляющий слой роутинга (например, Laravel из коробки уже не подойдёт), контроллеров и реквестов. Читай это как "написать свой фреймворк. У того же Laravel есть свой чётко работающий роутинг и любая попытка изменить принцип его работы сродни выстрелу себе в ногу.

Так что для команд, разрабатывающих с нуля фреймворк под компанию или же тех кто "перепрыгивает" с одного фрейма на другой раз в месяц, этот метод и может быть полезен, но с моей точки зрения это, от части, парализует работу бэкендеров и неоправданно добавит сложность в процесс разработки.

Вдобавок, он выглядит как GraphQL в режиме JSON вместо YAML. По сути, те же яйца видом сбоку.

Лично я бы не стал работать с JSON-RPC ровно никогда.

Я большой фанат CodeIgniter 4 (далее CI), считаю его лучшим PHP фреймворком для малых и средних по размеру API

После прочтения этого и просмотра приведенного примера в JsonRpcController.php понял что смысла дальше читать нет.

  • Десятиэтажные условия и прочие конструкции в контроллерах

  • Логика в контроллере

И как итог, грязные и толстые контроллеры - это ужас от которого как правило избавлялись еще лет 10-15, во всех компаниях. Складывается ощущение, что не желание / не умение делать лучше вы пытаетесь преподнести как некое благо когда - это жуткий моветон. Скажем так, глядя в подобный контроллер можно сделать "печальные" выводы о компании и культуре кодирования в ней.

Поддерживаю автора. Сам кайфую от удобства и легкости стандарта. Но в процессе понял что не хватает библиотек по документации. Сам я пишу на ноде и набросал небольшую библиотеку по автоматической документации в формате JS-Doc. Сейчас ищу мейнтейнеров на проект, сам он развернут на гитхабе, код открыт здесь - https://github.com/qertis/openapi-jsonrpc-jsdoc

if ($this->request->currentUser ?? NULL) {
$payload->context->user = $this->request->currentUser;
}

Что это за странная конструкция, объясните плиз? Она присвоит NULL в $this->request->currentUser в случае отсутствия, или что? Если нет, то зачем этот оператор вообще? А если да, то зачем это присваивать в $this->request ? Почему нельзя просто сделать $payload->context->user = $this->request?->currentUser;?

Только не предотвращает, а подавляет. Предотвращает ошибки валидация входящих данных. Но ленивые разработчики предпочитают подавлять ошибки, а не предотвращать их.

Я знаю, что это за оператор. Я не понимаю, почему здесь именно так написано.

Я правильно понимаю, что интерпретатор перейдёт к выражению после ?? в случае, если NULL обнаружится на любом уровне, то есть, гипотетически, что будет, если у нас не существует $this->request ?

Это очень смешная конструкция, которая не присвоит $payload->context->user вообще ничего, если $this->request->currentUser не существует. То есть продолжит дурацкую практику работы с несуществующими переменными. Плюс использует ?? не по назначению. То, что хотел написать автор, пишется вот так

if (isset($this->request->currentUser)) {
    $payload->context->user = $this->request->currentUser;
}

Но правильнее было бы написать вот так

$payload->context->user = $this->request->currentUser ?? null;

Чтобы присвоить $payload->context->user либо значение $this->request->currentUser, если оно существует, либо null.

Sign up to leave a comment.

Articles