⬆️ ⬇️

Simple API gateway based on PHP and Lumen

The term “microservices” is widely known today - it has suddenly become very fashionable, and many companies announce the transition to this architectural pattern even without really understanding it. However, the discussion of the usefulness of microservices is left outside of this article.



Traditionally, before the collection of microservices, an additional layer is offered - the so-called API gateway, which solves several problems at once (they will be listed later). At the time of this writing, there are almost no open source implementations of such gateways, so I decided to write my own in PHP using the Lumen microlimmer (part of Laravel).



In this article, I will show how simple this task is for modern PHP!



What is API gateway?



In short, the API gateway is a smart proxy server between users and any number of services (API), hence the name.

')

The need for this layer appears immediately upon transition to the microservice pattern:





More benefits are just those that came to mind in 10-20 seconds.



Nginx released a good free e-book dedicated to microservices and API gateway - I advise you to read to anyone who is interested in this pattern.



Existing options





As I said above, there are very few options, and those appeared relatively recently. Many opportunities in them yet.



Why PHP and Lumen?



With the release of version 7, PHP has become highly productive, and with the advent of frameworks like Laravel and Symfony, PHP has proven to the world that it can be beautiful and functional. Lumen, being a “cleared” fast version of Laravel, is ideal here, because we will not need sessions, templates and other features of the full stack of applications.



In addition, I just have more experience with PHP and Lumen, and deploying the resulting application through Docker - the language in which it is written will not be important for future users. This is just a layer that performs its role!



Selected terminology



I propose the following architecture and its corresponding terminology. In the code I will adhere to these terms in order not to get confused:





The application itself decided to call Vrata , because the “gate” in Russian is almost “gateway”, and the world doesn’t have enough applications with Russian names)



Directly behind the "gate" is the number of N microservices - API services that can respond to web requests. Each service can have any number of instances, so the API gateway will select a specific instance through the so-called service registry.



Each service offers some amount of resources (in the REST language), and each resource can have several possible actions. A fairly simple and logical structure for any experienced REST programmer.



Vrata requirements



Having not yet started the code, you can immediately determine some requirements for the future application:





I will say right away that most of the points are already working, and it was very easy to implement them. After all, the truth is told - we live in the best era for the programmer!



Implementation



Authentication



There was almost no work in this direction - it was enough to fix Laravel Passport for Lumen and we received the support of all modern OAuth2 features, including JWT. My little package port is published on GitHub / Packagist and someone is already installing it.



Routes and controller



All downstream routes from microservices are imported into Vrata from the configuration file in JSON format. At the time of launch, these routes are added to the service provider:



//    –     $registry = $this->app->make(RouteRegistry::class); //   Lumen   ,      $registry->bind(app()); 


Meanwhile, in the database of routes:



  /** * @param Application $app */ public function bind(Application $app) { //   -      Lumen //           //  middleware   OAuth2,      $this->getRoutes()->each(function ($route) use ($app) { $method = strtolower($route->getMethod()); $app->{$method}($route->getPath(), [ 'uses' => 'App\Http\Controllers\GatewayController@' . $method, 'middleware' => [ 'auth', 'helper:' . $route->getId() ] ]); }); } 


Now every public (and allowed in the configs) route from microservices has a route to the API gateway. In addition, synthetic or merged requests that exist only on this gateway are also added. All requests go to the same controller:



This is how the controller handles any GET request:



  /** * @param Request $request * @param RestClient $client * @return Response */ public function get(Request $request, RestClient $client) { //     ,  -  $parametersJar = $request->getRouteParams(); //     N   $output = $this->actions->reduce(function($carry, $batch) use (&$parametersJar, $client) { //  N    $responses = $client->asyncRequest($batch, $parametersJar); //        $parametersJar = array_merge($parametersJar, $responses->exportParameters()); //     -  array reduce return array_merge($carry, $responses->getResponses()->toArray()); }, []); //    .    JSON return $this->presenter->format($this->rearrangeKeys($output), 200); } 


Guzzle was chosen as the HTTP client, which copes with async requests perfectly and also has ready integration tools.



Composite requests



Complex compound requests are already working — this is when one route on the gateway corresponds to any number of routes on different microservices. Here is a working example:



 // Boolean-,    'aggregate' => true, 'method' => 'GET', //     ,       "jar" 'path' => '/v2/devices/{mac}/extended', //     'actions' => [ 'device' => [ //      'service' => 'core', 'method' => 'GET', 'path' => 'devices/{mac}', //        'sequence' => 0, //          -    'critical' => true ], 'ping' => [ 'service' => 'history', //         'output_key' => false, 'method' => 'POST', 'path' => 'ping/{mac}', 'sequence' => 0, 'critical' => false ], 'settings' => [ 'service' => 'core', //     JSON- 'output_key' => 'network.settings', 'method' => 'GET', //  ,     'device' 'path' => 'networks/{device%network_id}', 'sequence' => 1, 'critical' => false ] ] 


As you can see, complex routes are already available and have a good set of features - you can allocate critical ones, you can make parallel requests, you can use the response of one service in a request to another, and so on. In addition, the output is excellent performance - only 56 milliseconds to receive a cumulative response (loading Lumen and three background queries, all microservices with databases).



Service Registry



This is the weakest part so far - only one very simple method is implemented: DNS. Despite its primitiveness, it works fine in an environment like Docker Cloud or AWS, where the provider itself oversees a group of services and dynamically edits the DNS record.



At the moment, Vrata simply takes the hostname of the service, without delving into the cloud or one physical computer. The most popular registry for today, perhaps, is Consul , and it should be added as follows.



The essence of the registry is very simple - you need to keep a table of live and dead instances of the service, giving the address of specific instances when necessary. AWS and Docker Cloud (and many others) can do this for you, providing you with one magic hostname that always works.



Docker image



Speaking of microservices, it’s simply impossible not to mention Docker, one of the “hottest” technologies of the last couple of years. Microservices are usually tested and deployed as Docker images — this became standard practice, so we quickly prepared a public image in the Docker Hub.



One command entered in the terminal of any OS X, Windows or Linux machine, and my Vrata gateway works for you:



 $ docker run -d -e GATEWAY_SERVICES=... -e GATEWAY_GLOBAL=... -e GATEWAY_ROUTES=... pwred/vrata 


The entire configuration can be transferred in environment variables in JSON format.



Afterword



The application (gateway) is already used in practice in the company where I work. All code in a repository on GitHub . If someone wants to participate in the development - welcome :)



Since composite queries, both in concept and implementation, are very similar to the GraphQL query format promoted by Facebook (as opposed to REST), support for GraphQL queries is one of the priority future features.

Source: https://habr.com/ru/post/315128/



All Articles