SOA en Laravel y JSON-RPC 2.0

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 POSTsolicitud 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.0solicitud 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étodo
  • params matriz con parámetros
  • id 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"
  • resultcampo requerido para un resultado de consulta exitoso. No debe existir cuando ocurre un error
  • errorcampo obligatorio cuando se produce un error. No debe existir en un resultado exitoso
  • id 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.phpagregar

Route::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>/dataserá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 JsonRpcServervincula el método del controlador deseado con los parámetros pasados. Y devuelve la respuesta generada por la clase JsonRpcResponseen el formato de acuerdo con la especificación JSON-RPC 2.0descrita 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. DataCreateToda 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



All Articles