SOA (Service Oriented Architecture) se construye combinando e interactuando servicios acoplados libremente.Para demostrarlo, crearemos dos aplicaciones, Cliente y Servidor, y organizaremos su interacción utilizando el protocolo de llamada a procedimiento remoto JSON-RPC 2.0
.Cliente
La aplicación Cliente es un sitio para crear y mostrar cierto contenido. El cliente no contiene su propia base de datos, pero recibe y agrega datos debido a la interacción con la aplicación del servidor.En el cliente, la interacción proporciona una clase.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);
}
}
Necesitamos una biblioteca GuzzleHttp
, preinstalala.Formamos una POST
solicitud completamente estándar usando GuzzleHttp\Client
. La advertencia principal aquí es el formato de solicitud.De acuerdo con la especificación, la JSON-RPC 2.0
solicitud debería verse así:{
"jsonrpc": "2.0",
"method": "getPageById",
"params": {
"page_uid": "f09f7c040131"
},
"id": "54645"
}
jsonrpc
versión del protocolo, debe indicar "2.0"method
nombre del métodoparams
matriz con parámetrosid
ID de solicitud
Responder{
"jsonrpc": "2.0",
"result": {
"id": 2,
"title": "Index Page",
"content": "Content",
"description": "Description",
"page_uid": "f09f7c040131"
},
"id": "54645"
}
Si la solicitud se completó con un error, obtenemos{
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": "Parse error"
},
"id": "null"
}
jsonrpc
versión del protocolo, debe indicar "2.0"result
campo requerido para un resultado de consulta exitoso. No debe existir cuando ocurre un errorerror
campo obligatorio cuando se produce un error. No debe existir en un resultado exitosoid
Identificador de solicitud establecido por el cliente
El servidor forma la respuesta, así que volveremos a ella.En el controlador, es necesario formar una solicitud con los parámetros necesarios y procesar la respuesta.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']]);
}
}
El formato de respuesta fija JSON-RPC hace que sea fácil ver si la solicitud fue exitosa y tomar alguna acción si la respuesta contiene un error.Servidor
Comencemos por configurar el enrutamiento. En el archivo routes/api.php
agregarRoute::post('/data', function (Request $request, JsonRpcServer $server, DataController $controller) {
return $server->handle($request, $controller);
});
Todas las solicitudes recibidas por el servidor en la dirección <server_base_uri>/data
serán procesadas por la claseJsonRpcServer
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());
}
}
}
La clase JsonRpcServer
vincula el método del controlador deseado con los parámetros pasados. Y devuelve la respuesta generada por la clase JsonRpcResponse
en el formato de acuerdo con la especificación JSON-RPC 2.0
descrita anteriormente.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,
];
}
}
Queda por agregar un 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;
}
}
No veo ninguna razón para describir el controlador en detalle, métodos bastante estándar. DataCreate
Toda la lógica de crear un objeto se recopila en la clase , así como una verificación de la validez de los campos con el lanzamiento de la excepción necesaria.Conclusión
Intenté no complicar la lógica de las aplicaciones en sí, sino centrarme en su interacción.Los pros y los contras de JSON-RPC están bien escritos en un artículo, un enlace al que dejaré a continuación. Este enfoque es relevante, por ejemplo, al implementar formularios integrados.Referencias