Хабрахабр

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 неплохо написано в статье, ссылку на которую я оставлю ниже. Такой подход актуален, например, при реализации встраиваемых форм.

Ссылки

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»