SOA sur Laravel et JSON-RPC 2.0

SOA (Service Oriented Architecture) est construit en combinant et en interagissant des services faiblement couplés.

Pour démontrer, nous allons créer deux applications, Client et Serveur, et organiser leur interaction à l'aide du protocole d'appel de procédure à distance JSON-RPC 2.0.

Client


L'application Client est un site de création et d'affichage de certains contenus. Le client ne contient pas sa propre base de données, mais reçoit et ajoute des données en raison de l'interaction avec l'application serveur.

Sur le client, l'interaction fournit une 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);
    }
}

Nous avons besoin d'une bibliothèque GuzzleHttp, pré-installez-la.
Nous formons une POSTdemande complètement standard en utilisant GuzzleHttp\Client. La principale mise en garde ici est le format de la demande.
Selon la spécification, la JSON-RPC 2.0demande doit ressembler à:

{
    "jsonrpc": "2.0", 
    "method": "getPageById",
    "params": {
        "page_uid": "f09f7c040131"
    }, 
    "id": "54645"
}

  • jsonrpc version du protocole, doit indiquer "2.0"
  • method nom de la méthode
  • params tableau avec paramètres
  • id identifiant de demande

Réponse

{
    "jsonrpc": "2.0",
    "result": {
        "id": 2,
        "title": "Index Page",
        "content": "Content",
        "description": "Description",
        "page_uid": "f09f7c040131"
    },
    "id": "54645"
}

Si la demande s'est terminée avec une erreur, nous obtenons

{
    "jsonrpc": "2.0",
    "error": {
        "code": -32700,
        "message": "Parse error"
    },
    "id": "null"
}

  • jsonrpc version du protocole, doit indiquer "2.0"
  • resultchamp obligatoire pour un résultat de requête réussi. Ne devrait pas exister en cas d'erreur
  • errorchamp obligatoire en cas d'erreur. Ne devrait pas exister en cas de succès
  • id identifiant de demande défini par le client

Le serveur constitue la réponse, nous y reviendrons donc.

Dans le contrôleur, il est nécessaire de former une demande avec les paramètres nécessaires et de traiter la réponse.

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']]);
    }
}

Le format de réponse fixe JSON-RPC permet de voir facilement si la demande a réussi et de prendre des mesures si la réponse contient une erreur.

Serveur


Commençons par configurer le routage. Dans le fichier, routes/api.phpajoutez

Route::post('/data', function (Request $request, JsonRpcServer $server, DataController $controller) {
    return $server->handle($request, $controller);
});

Toutes les demandes reçues par le serveur à l'adresse <server_base_uri>/dataseront traitées par la 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());
        }
    }
}

La classe JsonRpcServerlie la méthode de contrôleur souhaitée avec les paramètres passés. Et il retourne la réponse générée par la classe JsonRpcResponsedans le format selon la spécification JSON-RPC 2.0décrite ci-dessus.

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,
        ];
    }
}

Il reste à ajouter un contrôleur.

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;
    }
}

Je ne vois aucune raison de décrire le contrôleur en détail, des méthodes assez standard. La classe DataCreatecontient toute la logique de création d'un objet, ainsi que la vérification de la validité des champs avec le lancement de l'exception nécessaire.

Conclusion


J'ai essayé de ne pas compliquer la logique des applications elles-mêmes, mais de me concentrer sur leur interaction.
Les avantages et les inconvénients de JSON-RPC sont bien écrits dans un article, un lien auquel je laisserai ci-dessous. Cette approche est pertinente, par exemple, lors de l'implémentation de formulaires intégrés.

Références



All Articles