SOA (Service Oriented Architecture) is built by combining and interacting loosely coupled services.To demonstrate, we will create two applications, Client and Server, and organize their interaction using the remote procedure call protocol JSON-RPC 2.0
.Customer
The Client application is a site for creating and displaying certain content. The client does not contain its own database, but receives and adds data through interaction with the Server application.On the client, the interaction provides a classJsonRpcClient
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);
}
}
We need a library GuzzleHttp
, pre-install it.We form a completely standard POST
request using GuzzleHttp\Client
. The main caveat here is the request format.According to the specification, the JSON-RPC 2.0
request should look like:{
"jsonrpc": "2.0",
"method": "getPageById",
"params": {
"page_uid": "f09f7c040131"
},
"id": "54645"
}
jsonrpc
protocol version, must indicate "2.0"method
method nameparams
array with parametersid
request id
Answer{
"jsonrpc": "2.0",
"result": {
"id": 2,
"title": "Index Page",
"content": "Content",
"description": "Description",
"page_uid": "f09f7c040131"
},
"id": "54645"
}
If the request was completed with an error, we get{
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": "Parse error"
},
"id": "null"
}
jsonrpc
protocol version, must indicate "2.0"result
required field for a successful query result. Should not exist when an error occurserror
required field when an error occurs. Should not exist on successful outcomeid
request identifier set by client
The server forms the answer, so we will come back to it.In the controller, it is necessary to form a request with the necessary parameters and process the response.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']]);
}
}
The JSON-RPC fixed response format makes it easy to see if the request was successful and take any action if the response contains an error.Server
Let's start by setting up routing. In the file routes/api.php
addRoute::post('/data', function (Request $request, JsonRpcServer $server, DataController $controller) {
return $server->handle($request, $controller);
});
All requests received by the server at the address <server_base_uri>/data
will be processed by the classJsonRpcServer
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());
}
}
}
The class JsonRpcServer
binds the desired controller method with the passed parameters. And it returns the response generated by the class JsonRpcResponse
in the format according to the specification JSON-RPC 2.0
described above.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,
];
}
}
It remains to add a controller.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;
}
}
I see no reason to describe the controller in detail, quite standard methods. The class DataCreate
contains all the logic of creating an object, as well as checking for the validity of fields with the throwing of the necessary exception.Conclusion
I tried not to complicate the logic of the applications themselves, but to focus on their interaction.The pros and cons of JSON-RPC are well written in an article, a link to which I will leave below. This approach is relevant, for example, when implementing embedded forms.References