SOA на Laravel и JSON-RPC 2.0

от автора

SOA (Сервис-Ориентированная Архитектура) строится путём комбинации и взаимодействия слабо-связанных сервисов.
Для демонстрации взаимодействия создадим два приложения Клиент и Сервер.
А их взаимодействие организуем посредством протокола удаленного вызова процедур JSON-RPC 2.0.

Клиент

Приложение Клиент представляет собой сайт для создания и отображения некого контента. Клиент не содержит собственной базы данных, а получает и добавляет данные благодаря взаимодействию с приложением Сервер.

На клиенте взаимодействие обеспечивает класс JsonRpcClient

namespace ClientApp\Services;  use GuzzleHttp\Client; use GuzzleHttp\RequestOptions;  class JsonRpcClient {     const JSON_RPC_VERSION = '2.0';      const METHOD_URI = 'data';      protected $client;      public function __construct()     {         $this->client = new Client([             'headers' => ['Content-Type' => 'application/json'],             'base_uri' => config('services.data.base_uri')         ]);     }      public function send(string $method, array $params): array     {         $response = $this->client             ->post(self::METHOD_URI, [                 RequestOptions::JSON => [                     'jsonrpc' => self::JSON_RPC_VERSION,                     'id' => time(),                     'method' => $method,                     'params' => $params                 ]             ])->getBody()->getContents();          return json_decode($response, true);     } } 

Нам потребуется библиотека GuzzleHttp, предварительно устанавливаем ее.
Формируем вполне стандартный POST запрос с помощью GuzzleHttp\Client. Основной нюанс здесь заключается в формате запроса.
Согласно спецификации JSON-RPC 2.0 запрос должен иметь вид:

{     "jsonrpc": "2.0",      "method": "getPageById",     "params": {         "page_uid": "f09f7c040131"     },      "id": "54645" } 

  • jsonrpc версия протокола, должна быть указана «2.0»
  • method имя метода
  • params массив с параметрами
  • id идентификатор запроса

Ответ

{     "jsonrpc": "2.0",     "result": {         "id": 2,         "title": "Index Page",         "content": "Content",         "description": "Description",         "page_uid": "f09f7c040131"     },     "id": "54645" } 

Если запрос был выполнен с ошибкой, получаем

{     "jsonrpc": "2.0",     "error": {         "code": -32700,         "message": "Parse error"     },     "id": "null" } 

  • jsonrpc версия протокола, должна быть указана «2.0»
  • result обязательное поле при успешном результате запроса. Не должно существовать при возникновении ошибки
  • error обязательное поле при возникновении ошибки. Не должно существовать при успешном результате
  • id идентификатор запроса, установленный клиентом

Ответ формирует сервер, так что мы к нему еще вернемся.

В контроллере необходимо сформировать запрос с нужными параметрами и обработать ответ.

namespace ClientApp\Http\Controllers;  use App\Services\JsonRpcClient; use Illuminate\Http\Request; use Illuminate\Support\Facades\Redirect;  class SiteController extends Controller {     protected $client;      public function __construct(JsonRpcClient $client)     {         $this->client = $client;     }      public function show(Request $request)     {         $data = $this->client->send('getPageById', ['page_uid' => $request->get('page_uid')]);          if (empty($data['result'])) {             abort(404);         }          return view('page', ['data' => $data['result']]);     }      public function create()     {         return view('create-form');     }      public function store(Request $request)     {         $data = $this->client->send('create', $request->all());          if (isset($data['error'])) {             return Redirect::back()->withErrors($data['error']);         }          return view('page', ['data' => $data['result']]);     } } 

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

Сервер

Начнем с настройки роутинга. В файл routes/api.php добавим

Route::post('/data', function (Request $request, JsonRpcServer $server, DataController $controller) {     return $server->handle($request, $controller); }); 

Все запросы поступившие на сервер по адресу <server_base_uri>/data будут обработаны классом JsonRpcServer

namespace ServerApp\Services;  class JsonRpcServer {     public function handle(Request $request, Controller $controller)     {                 try {             $content = json_decode($request->getContent(), true);              if (empty($content)) {                 throw new JsonRpcException('Parse error', JsonRpcException::PARSE_ERROR);             }             $result = $controller->{$content['method']}(...[$content['params']]);              return JsonRpcResponse::success($result, $content['id']);         } catch (\Exception $e) {             return JsonRpcResponse::error($e->getMessage());         }     } } 

Класс JsonRpcServer связывает нужный метод контроллера с переданными параметрами. И возвращает ответ сформированный классом JsonRpcResponse в формате согласно спецификации JSON-RPC 2.0 описанной выше.

use ServerApp\Http\Response;  class JsonRpcResponse {     const JSON_RPC_VERSION = '2.0';      public static function success($result, string $id = null)     {         return [             'jsonrpc' => self::JSON_RPC_VERSION,             'result'  => $result,             'id'      => $id,         ];     }      public static function error($error)     {         return [             'jsonrpc' => self::JSON_RPC_VERSION,             'error'  => $error,             'id'      => null,         ];     } } 

Осталось добавить контроллер.

namespace ServerApp\Http\Controllers;  class DataController extends Controller {     public function getPageById(array $params)     {         $data = Data::where('page_uid', $params['page_uid'])->first();          return $data;     }      public function create(array $params)     {         $data = DataCreate::create($params);          return $data;     } } 

Не вижу смысла описывать подробно контроллер, вполне стандартные методы. В классе DataCreate собрана вся логика создания объекта, также проверка на валидность полей с выбросом необходимого исключения.

Вывод

Я старался не усложнять логику самих приложений, а сделать акцент на их взаимодействии.
Про плюсы и минусы JSON-RPC неплохо написано в статье, ссылку на которую я оставлю ниже. Такой подход актуален, например, при реализации встраиваемых форм.

Ссылки

ссылка на оригинал статьи https://habr.com/ru/post/499626/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *