Для передачи данных по сети есть хорошо зарекомендовавшие себя стандарты. Среди основных: SOAP, gRPC, AMQP, REST, GraphQL.

При создании вебсайтов малой, средней и большой сложности с потоками данных к бэкенду и обратно в JSON формате обычно используются последние два с их вариантами. Верней, только варианты, потому что REST и GraphQL - ресурсо-ориентированные стандарты. Это как бы просто перенос элементарной работы с базой данных на клиента (хотя под "ресурсом" может пониматься и абстракция). Обычно таких запросов не больше трети от всего бэкенд API.

Попытка сделать весь API максимально RESTful страшно раздувает код и грузит сеть. Потому что остальные две трети запросов - в форме команд на бэкенд проделать какие-то действия, слабо отображающиеся на CRUD над некими ресурсами. И вариантов послать такие запросы достаточно много. Даже, чересчур.

GET, POST, PUT, PATCH, HTTP заголовки, куки, body, данные форм, GET query параметры, json, content-type, HTTP коды... Когда в команде несколько программеров, и у каждого свой взгляд на мироустройство, довольно быстро это превращается в винегрет. Даже один фулстэк разработчик часто оказывается в тупике перед всем этим месивом параметров, глаголов и существительных RESTlike API в непонимании как дальше с этим жить.

Всё это привело к созданию простой и понятной, но с моей точки зрения сильно недооцененной спецификации JSON-RPC (https://www.jsonrpc.org/specification), которая отделяет бизнес-логику клиент-серверного запроса  от самого сетевого протокола (HTTP) с его богатым, но не всегда нужным внутренним миром.

В JSON-RPC все запросы стандартизовано идут через HTTP POST в форме JSON объекта (в принципе, JSON-RPC 2.0 - это транспорто-независимый протокол, но мы рассматриваем наиболее частое его употребление). Обмен данными строгий и понятный. В запросе есть method и params. Method играет роль эндпойнта/команды, params - параметров. Ответы сервера приходят примерно в таком же виде.

RPC означает Remote procedure call, то есть, на бэкенд посылается команда о выполнении некоего кода. Команда по смыслу и предназначению может быть любой. В этом отличие RPC от REST, ограниченного четырьмя действиями CRUD на неких ресурсах.

JSON в названии означает, что обмен информацией между клиентом и сервером (микросервисами) идет через данные JSON формате.

Подробней о JSON-RPC можно прочитать, например, тут - https://habr.com/ru/post/441854/ или во многих других местах. Ничего сложного. В данной статье я хотел бы сконцентрироваться именно на его плюсах, а не описании.

Также, для задумывающихся о грамотном дизайне networked API рекомендую статьи Google на эту тему - https://cloud.google.com/apis/design. Архитектура для API не менее важна, как и для самой программной системы. Есть даже такое направление - API Driven Architecture, хотя по-моему опыту сперва обычно всё-таки делается какая-то альфа версия приложения, а потом уже рефакторится и унифицируется его API.

Плюсы

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

Что значит это для фронтэндера? Я обычно делаю модуль доступа к API и работаю через него:

import api from '@/api';

api.products.list();
api.users.update(userId, {"balance", 100});

Внутри list() и update() запросы через fetch() или axios() к нужным эндпойнтам backend API. Переход на стандарт JSON-RPC несложен, может быть проведен постепенно и особо профита в кодинге не приносит, кроме отсутствия необходимости задумываться, что использовать - POST, PUT или PATCH, и как передавать параметры, как обрабатывать приходящий результат, ошибки и т.п.

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

На бэке каждый запрос обрабатывается контроллером (которые сейчас по сути являются уже рудиментом MVC архитектурного шаблона времён генерации контента на сервере), в который передаются (условно) HttpRequest и HttpMessage. Один контроллер может обрабатывать несколько эндпойнтов, это прописывается в роутинге фреймворка. В контроллерах есть доступ к деталям транспортного (HTTP) протокола. Зачастую в контроллерах лежит вся бизнес логика, включая работу с БД и другими внутренними сервисами.

Что происходит, когда вы решаете сменить фреймворк, потому что нашли лучше? Вы переписываете контроллеры. Что происходит, когда ваш фреймворк радикально обновился, и вам нравятся эти новшества, вы хотите их использовать, но там много breaking change изменений? Вы переписываете контроллеры. А это бОльшая часть кода. И перейти на новую версию постепенно довольно сложно, только разом. Всё потому что ваша логика встроена в фреймворк.

А что с JSON-RPC? Там есть только один контроллер, который обрабатывает все запросы и перенаправляет их на нужные модули своим внутренним роутингом. Вот весь HTTP роутинг в CI:

$routes->post('rpc', 'JsonRpcController::index');

Вот файл внутреннего роутинга, используемый JsonRpcController-ом:

JsonRpcRoutes.php
<?php

namespace App\Controllers;

class JsonRpcRoutes
{

    public static $basePath = "App\src\\";

    public static $routes = [
        "users" => [
            "transactions:list" => "Users\Transactions::list",
            "withdrawal:create" => "Users\Transactions::createWithdrawal",
        ],
        "utils" => [
            "resources:list" => "Utils\Resources::list",
            "resources:update" => "Utils\Resources::update",
            "resources:getByKey" => "Utils\Resources::getByKey",
            "resources:updateByKey" => "Utils\Resources::updateByKey",
            "resourceCache:clear" => "Utils\Resources::clearCache",
        ],
    ];


    public static function route($method) {
        $path = explode('.' , $method);
        $route = self::$routes;
        foreach ($path as $step) {
            $route = $route[$step];            
        }
        return $route;
    }


    
}

Иерархическая модель в $routes задана для удобства. Функция route() берет метод запроса (method - "плоский" вариант роута, например, users.transactions:list) и выдает соответствующий ему класс и функцию на нем.

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

Utils\Resources и Users\Transactions - просто PHP классы, отвечающие за бизнес логику. Им приходят данные в виде объекта, и они отдают результат в виде объекта. Никакой связи с HTTP протоколом. Если потребуется сменить фреймворк, то нужно будет переписать только один файл - JsonRpcController.php

Ну и он сам:

JsonRpcController.php
<?php

namespace App\Controllers;

use CodeIgniter\Controller;
use stdClass;

class JsonRpcController extends Controller
{
    public function index()
    {
        try {
            $payloadData = $this->request->getJSON();
        } catch (\Throwable $th) {
            return $this->response->setJSON($this->errorResponse(-32700));
        }        
        $response = null;
        
        try {            
            // batch payload
            if (is_array($payloadData)) {
                if (count($payloadData) == 0) {
                    return $this->response->setJSON($this->errorResponse(-32600));
                }
                $response = [];
                foreach ($payloadData as $payload) {
                    $singleResponse = $this->processRequest($payload);
                    if ($singleResponse != null) {
                        $response[] = $singleResponse;
                    }
                }
                if (count($response) > 0) {
                    return $this->response->setJSON($response);
                }
            // single request
            } else if (is_object($payloadData)) {
                $response = $this->processRequest($payloadData);
                return $this->response->setJSON($response);
            } else {
                return $this->response->setJSON($this->errorResponse(-32700));
            }
        } catch (\Throwable $th) {
            return $this->response->setJSON($this->errorResponse(-32603, null, [
                "msg" => $th->getMessage(),
                "trace" => $th->getTrace()
            ]));
        }
    }

    /**
     * Process single JSON-RPC request.
     *
     * @param object    $paylod Request object
     * @return object   Response object
     */
    private function processRequest($payload) {
        if (!is_object($payload)) {
            return $this->errorResponse(-32700);
        }            
        if (!property_exists($payload, "jsonrpc") && !property_exists($payload, "method")) {
            return $this->errorResponse(-32600);
        }      
        $payload->context = new stdClass();
        if ($this->request->currentUser ?? NULL) {
            $payload->context->user = $this->request->currentUser;
        }

        $route = JsonRpcRoutes::route($payload->method);
        if (!$route) {
            return $this->errorResponse(-32601, $payload->id);
        }

        list($className, $methodName) = explode("::", $route);
        $className = JsonRpcRoutes::$basePath . $className;
        $outcome = (new $className())->$methodName($payload);

        if (!property_exists($payload, "id") || !$outcome) {
            return null;
        }
        
        $data = [
            "jsonrpc" => "2.0",
            "id" => $payload->id
        ];
        return array_merge($data, $outcome);
    }

    /**
     * Used for generic failures.
     *
     * @param int       $errorCode   according to JSON-RPC specification
     * @return Object   Response object for this error
     */
    private function errorResponse($errorCode, $id = null, $data = null) {
        $response = [
            "jsonrpc" => "2.0",
            "error" => [
                "code" => $errorCode,
                "message" => ''
            ],
            "id" => $id
        ];
        if ($data) {
            $response["error"]["data"] = $data;
        }
        switch ($errorCode) {
            case '-32600':
                $response["error"]["message"] = "Invalid Request";
                break;            
            case '-32700':
                $response["error"]["message"] = "Parse error";
                break;            
            case '-32601':
                $response["error"]["message"] = "Method not found";
                break;            
            case '-32602':
                $response["error"]["message"] = "Invalid params";
                break;            
            case '-32603':
                $response["error"]["message"] = "Internal error";
                break;            
            default:
                $response["error"]["message"] = "Internal error";
                break;
        }
        return $response;
    }

}

Контроллер мог бы быть в три-четыре раза меньше, но мне захотелось реализовать требования спецификации JSON-RPC 2.0 по максимуму, в итоге половина кода - корректная обработка всевозможных ошибок. По сути же он просто берет роут из JsonRpcRoutes и вызывает соответствующий метод на нужном классе, передавая ему параметры.

Да, в бэкэнд фреймворках есть другие часто используемые сервисы - например, для упрощения доступа к данным в БД (Active records (не ORM) в CodeIgniter 4 - очень эффектное решение, генерирующее эффективный SQL, возможно, единственное, ради чего стоит использовать CI, а не чистый rpc.php как вход с вебсервера), и их надо будет адаптировать при возможном переезде, но при желании модуль бизнес-логики (Utils\Resources и Users\Transactions) можно написать на чистом PHP/Java/Python, либо самостоятельно использовать сторонние библиотеки и быть полностью независимым от фреймворка. А ведь фреймворки очень хотят привязать разработчиков к себе.

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

Я уж не говорю о переиспользовании кода: вызвать из одного контроллера метод другого, чтобы применить его ответ для выдачи клиенту - задача не тривиальная. Потому что вход и выход в контроллерах - через протоколозависимые шлюзы фреймворка. Да, можно вытащить всю логику из контроллеров и использовать их как пустые обертки/прокси - но в чем тогда будет их смысл? Да и именно это и делает JSON-RPC, - в том числе.

Нынешние бэкенд фреймворки создавались когда всё еще генерировалось на сервере и клиенту посылался готовый html. Для всего этого нужны были серверные сессии, шаблонизаторы и прочие штуки серверных фреймворков. Для современных SPA-ориентированных бэкенд APIs всё это не нужно, это тяжелый груз, который часто как кандалы мешает работать с современными технологиями.

Вывод

JSON-RPC дарит разработчикам свободу.

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

Я бы даже сказал, что JSON-RPC - единственный стандарт, который технически полностью можно реализовать при построении коммуникации с бэкендом в 90% современных web app и мобильных приложений (если не смотреть в сторону gRPC и экзотики). И который реально приносит пользу в DX. Попытка реализации других стандартов будет большим компромиссом (просто оставлю здесь слово "HATEOAS").

Во второй части я остановлюсь на нескольких особенностях при работе с JSON-RPC, а именно: зачем нужны batch пакеты, аутентификация и авторизация, как видеть в DevTools / Network и логах вебсервера семантически понятную информацию о запросах, а не просто /rpc, и как стать господином бэкэнд фреймворка, а не его рабом.

Комментарии (34)


  1. nin-jin
    08.01.2023 12:43

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


    1. gmtd Автор
      08.01.2023 12:54
      +3

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


      1. nin-jin
        08.01.2023 13:11
        -2

        И в этом тоже.


        1. gmtd Автор
          08.01.2023 13:14

          GraphQL это язык

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

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


          1. nin-jin
            08.01.2023 13:38
            -6

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


            1. igrishaev
              08.01.2023 16:36
              +3

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


              1. Gruzchick
                08.01.2023 20:43

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


              1. nin-jin
                09.01.2023 01:19
                -2

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


                1. Cerberuser
                  09.01.2023 08:36

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


                  1. nin-jin
                    09.01.2023 08:59
                    -1

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


                1. igrishaev
                  09.01.2023 14:58

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


                  1. nin-jin
                    09.01.2023 15:38
                    -2

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


  1. kozlyuk
    08.01.2023 21:40
    +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" — адаптером самодельного роутинга к фреймворку.


    1. gmtd Автор
      08.01.2023 22:52

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

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

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

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

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

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


      1. kozlyuk
        08.01.2023 23:44
        +1

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

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


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

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


        1. pbatanov
          09.01.2023 00:48

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


          1. kozlyuk
            09.01.2023 09:42

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


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


            1. pbatanov
              09.01.2023 10:15

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

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

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


              1. kozlyuk
                09.01.2023 10:52

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


      1. FanatPHP
        09.01.2023 00:05

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


        1. gmtd Автор
          09.01.2023 09:18

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

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


          1. FanatPHP
            09.01.2023 09:44

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


    1. pbatanov
      09.01.2023 00:46
      +1

      В реальном приложении вряд ли удастся забыть про 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, ни в одной из них правильно его приготовить не смогли, в итоге везде получалось примерно это же самое, на что вы сетуете, оставался некий +- фиксированный конверт и "контракт", который можно было сделать гораздо удобней


  1. olku
    08.01.2023 23:30

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


    1. igrishaev
      09.01.2023 09:08
      +3

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


    1. gmtd Автор
      09.01.2023 09:09

      Я не работал с 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). Но опять же, это должен решать бэк, в целях безопасности.


  1. FanatPHP
    09.01.2023 09:49

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


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


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


    1. nin-jin
      09.01.2023 09:54

      1. pbatanov
        09.01.2023 10:24

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


        1. gmtd Автор
          09.01.2023 10:29
          -1

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

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

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

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

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

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


          1. pbatanov
            09.01.2023 10:32

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


          1. pbatanov
            09.01.2023 10:33

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


  1. igrishaev
    09.01.2023 15:01

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


  1. Helldar
    09.01.2023 16:29

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

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

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

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

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