by Enrico Zimuel
Principal Software Engineer @ Elastic
PHPDay, Verona, 10th May 2019
|
...the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API
- Martin Fowler
Source: Introduction to microservices
Source: Introduction to microservices
HAL-JSON (Hypertext Application Language JSON), Internet-Draft
GET /api/user/ezimuel
{
"_links": {
"self": {
"href": "http://domain/api/user/ezimuel"
},
"contacts": [
{ "href": "http://domain/api/user/mwop" },
{ "href": "http://domain/api/user/zeevs" }
]
},
"id": "ezimuel",
"name": "Enrico Zimuel"
}
Problem Details (RFC 7807)
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en
{
"type": "https://example.net/validation-error",
"title": "Your request parameters didn't validate.",
"invalid-params": [
{
"name": "age",
"reason": "must be a positive integer"
},
]
}
Global variables:
Common interfaces for representing HTTP messages as described in RFC 7230 and RFC 7231, and URIs for use with HTTP messages as described in RFC 3986
// Returns an empty array if not found:
$header = $request->getHeader('Accept');
// Returns an empty string if not found:
$header = $request->getHeaderLine('Accept');
// Test for a header:
if (! $request->hasHeader('Accept')) {}
// Get query string parameters
$query = $request->getQueryParams();
zendframework/zend-diactoros implements PSR-7
$response = $response->withStatus(418, "I'm a teapot");
$response = $response->withBody($body);
pecl install swoole
use Swoole\Http\Server;
$http = new Server("127.0.0.1", 9501);
$http->on("start", function ($server) {
echo "Started at http://127.0.0.1:9501\n";
});
$http->on("request", function ($request, $response) {
$response->header("Content-Type", "text/plain");
$response->end("Hello World\n");
});
$http->start();
Test: 16K req/sec on CPU i5-2500, 16 GB RAM, PHP 7.2.12, Swoole 4.2.9
A function that gets a request and generates a response
function ($request)
{
// do something with $request
return $response;
}
function ($request, callable $delegate)
{
// delegating $request to another middleware
$response = $delegate($request);
return $response;
}
function ($request, callable $delegate) use ($cache)
{
if ($cache->has($request)) {
return $cache->get($request);
}
$response = $delegate($request);
$cache->set($request, $response);
return $response;
}
Common interfaces for HTTP server request handlers and HTTP server middleware components that use HTTP messages as described by PSR-7
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface RequestHandlerInterface
{
public function handle(
ServerRequestInterface $request
): ResponseInterface;
}
An handler returns a response, without delegate
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface;
}
A middleware participates in processing an HTTP message, it may deletegate.
The PHP framework for middleware applications
You can install Expressive using composer:
composer create-project zendframework/zend-expressive-skeleton api
$app->get('/api/ping', function ($request) {
return JsonResponse(['ack' => time()])
});
// or implement a RequestHandlerInterface
$app->get('/api/ping', App\Handler\PingHandler::class);
namespace App\Handler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\JsonResponse;
class PingHandler implements RequestHandlerInterface
{
public function handle(
ServerRequestInterface $request
) : ResponseInterface {
return new JsonResponse(['ack' => time()]);
}
}
$app->pipe(ErrorHandler::class);
$app->pipe(ServerUrlMiddleware::class);
$app->pipe(RouteMiddleware::class);
$app->pipe(ImplicitHeadMiddleware::class);
$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(MethodNotAllowedMiddleware::class);
$app->pipe(UrlHelperMiddleware::class);
$app->pipe(ProblemDetailsMiddleware::class);
$app->pipe(DispatchMiddleware::class);
$app->pipe(NotFoundHandler::class);
/config/pipeline.php
$app->route('/api/users[/{id}]', [
Authentication\AuthenticationMiddleware::class,
Authorization\AuthorizationMiddleware::class,
Api\Action\UserAction::class
], ['GET', 'POST', 'PATCH', 'DELETE'], 'api.users');
// or route each HTTP method
$app->get('/api/users[/{id}]', ..., 'api.users.get');
$app->post('/api/users', ..., 'api.users.post');
$app->patch('/api/users/{id}', ..., 'api.users.patch');
$app->delete('/api/users/{id}', ..., 'api.users.delete');
Install:
composer require zendframework/zend-expressive-swoole
Usage:
vendor/bin/zend-expressive-swoole start
Open your browser at localhost:8080
Run a web application from CLI
Simplify the deploy (only 1 container)
A web server (nginx) can be used as load balancer
2-4x faster than Nginx and Apache
Req/sec (mean) | |
Nginx | 1418.23 |
Apache | 1915.62 |
Swoole | 4864.34 |
Testing environment:
Ubuntu 18.04, Expressive Skeleton 3.2.3, PHP 7.2.12, Nginx 1.14 + FPM,
Apache 2.4.29 + mod_php, Swoole 4.2.9, CPU i5-2500, 16 GB RAM, HD SSD
use Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()->build();
$params = [
'index' => 'my_index',
'type' => 'my_type',
'id' => 'my_id',
'body' => ['testField' => 'abc']
];
$response = $client->index($params);
print_r($response);
Contact me: enrico.zimuel [at] elastic.co
Follow me: @ezimuel
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.