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:
- A single address is much more convenient than hundreds (with Netflix there are more than 600) individual API addresses;
- It is logical to check the user data (token) in a single place, at the "entrance";
- It is convenient to implement restrictions on the number of requests in a single place;
- The whole system becomes more flexible - you can change the internal structure at least every day. Support for older versions of the API is becoming a trivial matter;
- You can cache or mutate responses;
- For the convenience of the user (or the developers of the front end), you can combine answers from different services. Facebook has long been offering this opportunity.
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:
- The gateway should scale horizontally, because it's 2016, and everyone wants to scale horizontally. Therefore, there should be no state of the application;
- The gateway must be able to combine requests and call microservices asynchronously;
- The gateway should be able to limit the number of requests in a period of time;
- The gateway must be able to validate the authentication token. It is traditionally proposed that the API gateway performs authentication, and microservices hidden under it perform authorization on their resources;
- The gateway should be able to automatically import available resources from microservices. To begin, select the Swagger format, as the most popular in the world today;
- The gateway must be able to change (mutate) microservice responses;
- And finally: the gateway should be perfectly launched directly from the Docker image and configured via environment variables. We do not want any additional repositories, deployment scripts and so on!
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:
Meanwhile, in the database of routes:
public function bind(Application $app) {
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:
public function get(Request $request, RestClient $client) {
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:
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.