SOA (Сервис-Ориентированная Архитектура) строится путём комбинации и взаимодействия слабо-связанных сервисов.
Для демонстрации создадим два приложения Клиент и Сервер и организуем их взаимодействие посредством протокола удаленного вызова процедур
Приложение Клиент представляет собой сайт для создания и отображения некого контента. Клиент не содержит собственной базы данных, а получает и добавляет данные благодаря взаимодействию с приложением Сервер.
На клиенте взаимодействие обеспечивает класс
Нам потребуется библиотека
Формируем вполне стандартный
Согласно спецификации
Ответ
Если запрос был выполнен с ошибкой, получаем
Ответ формирует сервер, так что мы к нему еще вернемся.
В контроллере необходимо сформировать запрос с нужными параметрами и обработать ответ.
Фиксированный формат ответа JSON-RPC позволяет легко понять успешным ли был запрос и применить какие-либо действия, если ответ содержит ошибку.
Начнем с настройки роутинга. В файл
Все запросы поступившие на сервер по адресу
Класс
Осталось добавить контроллер.
Не вижу смысла описывать подробно контроллер, вполне стандартные методы. В классе
Я старался не усложнять логику самих приложений, а сделать акцент на их взаимодействии.
Про плюсы и минусы JSON-RPC неплохо написано в статье, ссылку на которую я оставлю ниже. Такой подход актуален, например, при реализации встраиваемых форм.
Для демонстрации создадим два приложения Клиент и Сервер и организуем их взаимодействие посредством протокола удаленного вызова процедур
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 неплохо написано в статье, ссылку на которую я оставлю ниже. Такой подход актуален, например, при реализации встраиваемых форм.
Ссылки
- Подробнее про протокол взаимодействия читаем здесь jsonrpc.org/specification
- REST? Возьмите тупой JSON-RPC статья описывающая плюсы и минусы JSON-RPC