Workshop

Develop microservices (and web APIs) in PHP


by Enrico Zimuel
Principal Software Engineer @ Elastic


Cloud Conf, Turin, 27th March 2019

About me

Microservice

...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

Benefit

  • Separation of concerns
    • Modularity
    • Encapsulation
  • Scalability
    • Horizontally scaling
    • Workload partitioning

Cons

  • Network latency
  • Debugging
  • New architecture challenges:
    • Autodiscovery
    • Telemetry
    • Everything needs to be automated

PHP

Microservices in PHP

  • PHP is easy to deploy
  • PHP 7 is super fast!
  • Big community
  • Libraries/Frameworks
  • Async in PHP (Swoole, ReactPHP, ...)

PHP 7 speed


$a = [];
for ($i = 0; $i < 1000000; $i++) {
  $a[$i] = ["hello"];
}
echo memory_get_usage(true);
PHP 5.6 PHP 7.2
Memory 428 MB 34 MB
Execution time 0.59 sec 0.06 sec

Other languages

PHP 7 is faster than Python 3!

PHP 7 migration: case sudy

  • Badoo saves 1 million dollars per year (source)
  • Tumblr reduced the latency and CPU load by half (source)
  • Dailymotion handles twice more traffic with same infrastructure (source)

Web APIs

HTTP IN & OUT

Example

HTTP Request:


GET /api/version

HTTP Response:


HTTP/1.1 200 OK
Connection: close
Content-Length: 17
Content-Type: application/json

{
  "version": "1.0"
}
	

Building a Web API

  • Managing the HTTP request and response
  • Choosing a representation format
  • Choosing an error format
  • Filtering & validating input data
  • Authenticating HTTP requests
  • Authorizing HTTP requests
  • Documentation

HTTP Methods (REST approach)

  • GET: read a resource
  • HEAD: read without body
  • POST: create a resource
  • PUT: replace a resource
  • PATCH: update a resource
  • DELETE: delete a resource
  • OPTIONS: information about a resource

HTTP Status-code

  • Informational 1xx: 100 Continue
  • Successful 2xx: 200 OK, 201 Created, 202 Accepted
  • Redirection 3xx: 301 Moved Permanently, 307 Temporary Redirect
  • Client Error 4xx: 401 Unauthorized, 404 Not Found, 405 Method Not Allowed
  • Server Error 5xx: 500 Internal Server Error, 503 Service Unavailable

Representation format

  • JSON (JavaScript Object Notation)
    in PHP json_encode(), json_decode()
  • XML (eXtensible Markup Language)
    in PHP SimpleXML, libxml, DOM, etc

HAL-JSON

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

Error format

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

Authentication

Documentation

HTTP in PHP

HTTP message in PHP

Global variables:

  • $_SERVER
  • $_POST
  • $_GET
  • $_FILES
  • $_COOKIE

HTTP functions in PHP

  • http_response_code()
  • header(), header_remove(), headers_list(), headers_sent()
  • setcookie(), setrawcookie()
  • gethostname(), etc

Manage HTTP in PHP

  • PHP does not offer an object representation of HTTP request/response
  • Unfortunately, It's quite hard to manage HTTP messages in PHP!

PHP-FIG

PHP FIG

  • PHP Framework Interop Group (PHP FIG)
  • A working group for defining common standards to interop between PHP frameworks/libraries
  • PHP Standards Recommendations (PSR)
  • More information at php-fig.org

PSR-7

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

PSR-7 interfaces

  • github: psr/http-message
  • Psr\Http\Message\MessageInterface
  • Psr\Http\Message\RequestInterface
  • Psr\Http\Message\ResponseInterface
  • Psr\Http\Message\ServerRequestInterface
  • Psr\Http\Message\StreamInterface
  • Psr\Http\Message\UploadedFileInterface
  • Psr\Http\Message\UriInterface

Example


// Returns an empty array if not found:
$header = $message->getHeader('Accept');
// Returns an empty string if not found:
$header = $message->getHeaderLine('Accept');
// Test for a header:
if (! $message->hasHeader('Accept')) {
}
// If the header has multiple values, fetch them
// as an array:
$values = $message->getHeader('X-Foo');
// Or as a comma-separated string:
$values = $message->getHeaderLine('X-Foo');

Immutability

  • PSR-7 Request and Response model immutability
  • Messages are modeled as value objects; a change to any value results in a new instance
  • PSR-7 Stream does not model immutability

zendframework/zend-diactoros implements PSR-7

Example


$response = $response->withStatus(418, "I'm a teapot");

$query   = $request->getQueryParams();
$body    = $request->getBodyParams();

$request = $request->withBodyParams(json_decode($body));

PSR-7 implementation

Swoole

  • Swoole is an async programming framework for PHP 7
  • PHP extension, install:
    
    pecl install swoole
    
  • Released under Apache license 2.0
  • More info at swoole.co.uk

Features

  • Event-driven, asynchronous programming for PHP
  • Async TCP / UDP / HTTP / Websocket / HTTP2 client/server side API
  • IPv4 / IPv6 / Unixsocket / TCP/ UDP and SSL / TLS support
  • High performance and scalable
  • Fast serializer / unserializer
  • Milliseconds task scheduler

Swoole vs. PHP-FPM

  • Forks a number of worker processes based on CPU core number
  • Fupports Long-live connections
  • Manage and reuse the status in memory
  • Executes Non-blocking code (async, coroutine)

HTTP Server


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

Event loop

Middleware

Middleware

A function that gets a request and generates a response


function ($request)
{
    // do something with $request
    return $response;
}

Delegating middleware


function ($request, callable $delegate)
{
    // delegating $request to another middleware
    $response = $delegate($request);
    return $response;
}

Middleware onion

Execution pipeline

Example: cache


function ($request, callable $delegate) use ($cache)
{
    if ($cache->has($request)) {
        return $cache->get($request);
    }
    $response = $delegate($request);
    $cache->set($request, $response);
    return $response;
}

PSR-15

Common interfaces for HTTP server request handlers and HTTP server middleware components that use HTTP messages as described by PSR-7

PSR-15: Handler


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

PSR-15: Middleware


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.

Expressive

The PHP framework for middleware applications

Installation

You can install Expressive using composer:

composer create-project zendframework/zend-expressive-skeleton api

Routes


$app->get('/api/ping', function ($request) {
    return JsonResponse(['ack' => time()])
});

// or implement a RequestHandlerInterface
$app->get('/api/ping', App\Handler\PingHandler::class);

PingHandler


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

Pipeline


$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

Route a REST API


$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');

Tools for Web API

Expressive with Swoole

Install:


composer require zendframework/zend-expressive-swoole

Usage:


vendor/bin/zend-expressive-swoole start

Open your browser at localhost:8080

PHP + Expressive + Swoole

Run a web application from CLI

Simplify the deploy (only 1 container)

A web server (nginx) can be used as load balancer

Benchmark

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

Elasticsearch

  • Elasticsearch is a highly scalable open-source full-text search and analytics engine
  • Near Realtime (NRT)
  • Designed for scale (horizontally)
  • REST API
  • Based on Lucene
  • More information here

Elasticsearch & PHP

Example


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);
  • Kibana is an open source data visualization plugin for Elasticsearch
  • It provides visualization capabilities on top of the content indexed on an Elasticsearch cluster
  • More information here

Hands-on

github.com/ezimuel/php-microservices-workshop

References

Thanks!

Contact me: enrico.zimuel [at] elastic.co

Follow me: @ezimuel



Creative Commons License
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.