A SOA (Arquitetura Orientada a Serviços) é criada combinando e interagindo serviços de acoplamento fraco.Para demonstrar, criaremos dois aplicativos, Cliente e Servidor, e organizaremos sua interação usando o protocolo de chamada de procedimento remoto JSON-RPC 2.0
.Cliente
O aplicativo cliente é um site para criar e exibir determinado conteúdo. O cliente não contém seu próprio banco de dados, mas recebe e adiciona dados devido à interação com o aplicativo Servidor.No cliente, a interação fornece uma classeJsonRpcClient
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);
}
}
Precisamos de uma biblioteca GuzzleHttp
, pré-instale-a.Formamos uma POST
solicitação completamente padrão usando GuzzleHttp\Client
. A principal ressalva aqui é o formato de solicitação.De acordo com a especificação, a JSON-RPC 2.0
solicitação deve se parecer com:{
"jsonrpc": "2.0",
"method": "getPageById",
"params": {
"page_uid": "f09f7c040131"
},
"id": "54645"
}
jsonrpc
versão do protocolo, deve indicar "2.0"method
nome do métodoparams
matriz com parâmetrosid
Identificação do Pedido
Responda{
"jsonrpc": "2.0",
"result": {
"id": 2,
"title": "Index Page",
"content": "Content",
"description": "Description",
"page_uid": "f09f7c040131"
},
"id": "54645"
}
Se a solicitação foi concluída com um erro, obtemos{
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": "Parse error"
},
"id": "null"
}
jsonrpc
versão do protocolo, deve indicar "2.0"result
campo obrigatório para um resultado de consulta bem-sucedido. Não deveria existir quando ocorre um erroerror
campo obrigatório quando ocorrer um erro. Não deve existir em resultado bem sucedidoid
identificador de solicitação definido pelo cliente
O servidor forma a resposta, então retornaremos a ela.No controlador, é necessário formar uma solicitação com os parâmetros necessários e processar a resposta.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']]);
}
}
O formato de resposta fixa JSON-RPC facilita ver se a solicitação foi bem-sucedida e executar qualquer ação se a resposta contiver um erro.Servidor
Vamos começar configurando o roteamento. No arquivo, routes/api.php
adicioneRoute::post('/data', function (Request $request, JsonRpcServer $server, DataController $controller) {
return $server->handle($request, $controller);
});
Todas as solicitações recebidas pelo servidor no endereço <server_base_uri>/data
serão processadas pela classeJsonRpcServer
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());
}
}
}
A classe JsonRpcServer
liga o método do controlador desejado com os parâmetros passados. E retorna a resposta gerada pela classe JsonRpcResponse
no formato de acordo com a especificação JSON-RPC 2.0
descrita acima.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,
];
}
}
Resta adicionar um controlador.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;
}
}
Não vejo razão para descrever o controlador em detalhes, métodos bastante padrão. A classe DataCreate
contém toda a lógica de criação de um objeto, além de verificar a validade dos campos com o lançamento da exceção necessária.Conclusão
Tentei não complicar a lógica dos aplicativos, mas focar na interação deles.Os prós e contras do JSON-RPC estão bem escritos em um artigo, um link ao qual deixarei abaixo. Essa abordagem é relevante, por exemplo, ao implementar formulários incorporados.Referências