In the following section, we will explain how to validate the input data in our microservice and how to manage the possible errors. It is important to filter the request we are receiving--not only to notify the consumer that the request was not valid, but also to avoid security problems or parameters that we are not expecting.
Lumen has a fantastic validation system, so we do not need to install anything to validate our data. Note that the following validation rules can be placed on the routes.php
or on the controller inside every function. We will use it inside the function to be clearer.
To use our database for the validation system, we need to configure it. This is very simple; we just need to create a config/database.php
file (and the folder) in our root with the following code:
<?php return [ 'default' => 'mysql', 'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'collation' => 'utf8_unicode_ci' ] ] ];
Then, you have to add the database line in the bootstrap/app.php
file:
$app->withFacades();
$app->withEloquent();
$app->configure('database');
Once you have done this, the Lumen validation system is ready. So, let's write the rules to validate the POST method to create a new secret on the Secrets
microservice:
public function create(Request $request) { $this->validate( $request, [ 'name' => 'required|string|unique:secrets,name', 'latitude' => 'required|numeric', 'longitude' => 'required|numeric', 'location_name' => 'required|string' ] );
In the preceding code, we confirm that the parameters should pass the rules. The field 'name'
is required; it is a string, and also it should be unique in the secrets
table. The field's 'latitude'
and 'longitude'
are numeric and required too. Also, the 'location_name'
field is required and it is a string.
In the Lumen documentation (https://lumen.laravel.com/docs), you can check out all the available options to validate your inputs.
You can try it out in your Postman; create a POST request with the following application/json
parameters to check a failed insertion (note that you can also send it like form-data key values):
{ "name": "amber", "latitude":"1.23", "longitude":"-1.23", "location_name": "test" }
The preceding request will try to validate a new secret with the same name as other previous records. From our validation rules, we are not allowing the consumers to create new secrets with the same name, so our microservice will respond with a 422
error with the following body:
{ "name": [ "The name has already been taken." ] }
Note that the status codes (or error codes) are very important to inform your consumers what happened with their requests; if everything is fine, it should respond with a 200
status code. Lumen returns a 200
status code by default.
In Chapter 11, Best Practices and Conventions, we will see a full list of all the available codes that you can use in your application.
Once the validation rules pass, we should save the data on the database. This is very simple in Lumen, just do this:
$secret = Secret::create($request->all()); if ($secret->save() === false) { // Manage Error }
After this, we will have our new record available in the database. Lumen provides other methods to create for other tasks such as fill, update, or delete.
It is necessary to know that we have to manage the possible errors that happen in our application. To do this, Lumen provides us with a list of exceptions that can be used.
So, now we will try to get an exception when trying to call another microservice. To do this, we will call the secret microservice from the user microservice.
Please remember that for security reasons if you didn't link a container with another container, they can't see each other. Edit your docker-compose.yml
and add the link from the microservice_user_fpm
to the microservice_secret_nginx
, as follows:
microservice_user_fpm:
build: ./microservices/user/php-fpm/
volumes_from:
- source_user
links:
- autodiscovery
- microservice_secret_nginx
expose:
- 9000
environment:
- BACKEND=microservice-user-nginx
- CONSUL=autodiscovery
Now, you should start your containers again:
docker-compose start
Also, remember that we need to install GuzzleHttp
as we did before on the Battle
microservice and on the User
microservice in order to call the Secret
microservice.
We will create a new function on the User
microservice to show the secrets kept in a user
wallet.
Add this to app/Http/routes.php
:
$app->get( 'user/{id}/wallet', 'UserController@getWallet' );
Then, we will create the method to get a secret from the user
wallet--for example, look at this:
public function getWallet($id) { /* ... Code ommited ... */ $client = new Client(['verify' => false]); try { $remoteCall = $client->get( 'http://microservice_secret_nginx /api/v1/secret/1'); } catch (ConnectException $e) { /* ... Code ommited ... */ throw $e; } catch (ServerException $e) { /* ... Code ommited ... */ } catch (Exception $e) { /* ... Code ommited ... */ } /* ... Code ommited ... */ }
We are calling the Secret
microservice, but we will modify the URI in order to get a ConnectException
, so please modify it:
$remoteCall = $client->get(
'http://this_uri_is_not_going_to_work'
);
Give it a try on Postman; you will receive a ConnectException
error.
Now, set the URI correctly again and put some wrong code on the secret microservice side:
public function get($id) { this_function_does_not_exist(); }
The preceding code will return an error 500 for the secret microservice; but we are calling it from the User
microservice, so now we will receive a ServerException
error.
There are hundreds of kinds of errors that you can manage in your microservice by catching them. In Lumen, all the exceptions are handled by the Handler
class (located at app/Exceptions/Handler.php
). This class has two defined methods:
report()
: This allows us to send our exceptions to external services--for example, a centralized logging system.render()
: This transforms our exception into an HTTP response.We will be updating the render()
method to return custom error messages and error codes. Imagine that we want to catch a Guzzle ConnectException
and return a more friendly and easy-to-manage error. Take a look at the following code:
/** Code omitted **/ use SymfonyComponentHttpFoundationResponse; use GuzzleHttpExceptionConnectException; /** Code omitted **/ public function render($request, Exception $e) { switch ($e) { case ($e instanceof ConnectException) : return response()->json( [ 'error' => 'connection_error', 'code' => '123' ], Response::HTTP_SERVICE_UNAVAILABLE ); break; default : return parent::render($request, $e); break; } }
Here, we are detecting the Guzzle ConnectException
and giving a custom error message and code. Using this strategy helps us to know what is failing and allows us to act according to the error we are dealing with. For example, we can assign the code 123
to all our connection errors; so, when we detect this issue, we can avoid a cascade failure in other services or notify the developer.
18.191.94.249