Implementing a microservice call

Now that we know how to make a call, let's create a more complex example. We will build our battle system. As mentioned in the previous chapters, a battle is a fight between two players in order to get secrets from the loser. Our battle system will consist of three rounds, and in each round, there will be a dice throw; the user that wins the most rounds will be the winner and will get a secret from the loser's wallet.

Tip

We suggest using some testing development practices (TDD, BDD, or ATDD) as we explained before; you can see some examples in the preceding chapter. We will not include more tests in this chapter.

In the Battle microservice, we can create a function for the battle in the BattleController.php file; let's look at a valid structure method:

    public function duel(Request $request) 
    {
        return response()->json([]);
    }

Do not forget to add the endpoint on the routes.php file to link the URI to our method:

    $app->post('battle/duel', 'BattleController@duel');

At this point, the duel method for the Battle microservice will be available; give it a try with Postman.

Now, we will implement the duel. We need to create a new class for the dice. To store a new class, we will create a new folder called Algorithm in the root, and the file Dice.php will include the dice methods:

    <?php
    namespace AppAlgorithm;
    class Dice
    {
        const TOTAL_ROUNDS   = 3;
        const MIN_DICE_VALUE = 1;
        const MAX_DICE_VALUE = 6;
        public function fight()
        {
            $totalRoundsWin = [
                'player1' => 0,
                'player2' => 0
            ];

            for ($i = 0; $i < self::TOTAL_ROUNDS; $i++) {
                $player1Result = rand(
                    self::MIN_DICE_VALUE,
                    self::MAX_DICE_VALUE
                );
                $player2Result = rand(
                    self::MIN_DICE_VALUE,
                    self::MAX_DICE_VALUE
                );
                $roundResult = ($player1Result <=> $player2Result);
                if ($roundResult === 1) {
                    $totalRoundsWin['player1'] = 
                    $totalRoundsWin['player1'] + 1;
                } else if ($roundResult === -1) {
                    $totalRoundsWin['player2'] = 
                    $totalRoundsWin['player2'] + 1;
                }
            }

            return $totalRoundsWin;
        }
    }

Once we have developed the Dice class, we will call it from the BattleController to see who wins a battle. The first thing is to include the Dice class on the BattleController.php file at the top, and then we can create an instance of the algorithm we will use for the duels (this is a good practice in order to change the duel system in the future; for example, if we want to use a duel based on energy points or card games, we would only need to change the Dice class for the new one).

The duel function will return a JSON with the battle results. Please look at the new highlighted code included on the BattleController.php:

    <?php
    namespace AppHttpControllers;
    use IlluminateHttpRequest;
    use AppAlgorithmDice;

    class BattleController extends Controller
    {
        protected $battleAlgorithm = null;
        protected function setBattleAlgorithm()
        {
            $this->battleAlgorithm = new Dice();
        }

        /** ... Code omitted ... **/

        public function duel(Request $request)
        {
            $this->setBattleAlgorithm();
            $duelResult = $this->battleAlgorithm->fight();

            return response()->json(
                [
                    'player1'     => $request->input('userA'),
                     'player2'     => $request->input('userB'),
                     'duelResults' => $duelResult
                 ]
            );
        }
    }

Give it a try using Postman; remember that it is an HTTP POST request to the URI http://localhost:8081/api/v1/battle/duel (note that we set up the port 8081 for the battle microservice on Docker), and it is necessary to send the parameters userA and userB with the usernames you want. If everything is correct, you should get a response similar to this:

    {
        "player1": "John",
        "player2": "Joe",
        "duelResults": {
            "player1": 2,
            "player2": 1
        }
    }

Request life cycle

The request life cycle is the map of a request until it is returned to the consumer as a response. It is interesting to understand this process in order to avoid issues during the request. Every framework has its own way of doing the request, but all of them are quite similar and follow some basic steps like Lumen does:

  1. Every request is managed by public/index.php. It does not have much code, it just loads the Composer-generated autoloader definition and creates the instance of the application from bootstrap/app.php.
  2. The request is sent to HTTP Kernel, which defines some necessary things such as error handling, logging, application environment, and other necessary tasks that should be added before the request is executed. HTTP Kernel also defines a list of middleware that the request must pass before retrieving the application.
  3. Once the request passes the HTTP Kernel and arrives at the application, it reaches the routes and tries to match it with the correct one.
  4. It executes the controller and the code that correspond to the route and creates and returns a response object.
  5. The HTTP headers and the response object content is returned to the client.

This is just a basic example of the request-response workflow; the real process is more complex. You should take into account that the HTTP Kernel is like a big black box that does things that are not visible to the developer in a first instance, so understanding this example is enough for this chapter.

Communication between microservices with Guzzle

One of the most important things in microservices is the communication between them. Most of the time a single microservice doesn't have all the information requested by the consumer, so the microservice needs to call to a different microservice to get the desired data.

For example, adhering to the last example for the duel between two users, if we want to give all the information about the users included in the battle in the same call and we don't  have a specific method to get the user information in the Battle microservice, we can request the user information from the user microservice. To achieve this, we can use the PHP core feature cURL or use an external library that wraps cURL, giving an easier interface as GuzzleHttp.

To include GuzzleHttp in our project, we only need to add the following line in the composer.json file of the Battle microservice:

    {
        // Code omitted
        "require": {
            "php": ">=5.5.9",
            "laravel/lumen-framework": "5.2.*",
            "vlucas/phpdotenv": "~2.2",
            "guzzlehttp/guzzle": "~6.0"
        },
        // Code omitted
    }

Once we save the changes, we can enter our PHP-FPM container and run the following command:

cd /var/www/html && compose update

GuzzleHttp will be installed and ready to use on the project.

In order to get the user information from the User microservice, we will build a method that will give the information to the Battle microservice. For now, we will store the user information in a database, so we have an array to store it. In the app/Http/Controllers/UserController.php of the User microservice, we will add the following lines:

    <?php

    namespace AppHttpControllers;
    use IlluminateHttpRequest;
    class UserController extends Controller
    {
        protected $userCache = [
            1 => [
                'name' => 'John',
                'city' => 'Barcelona'
            ],
            2 => [
                'name' => 'Joe',
                'city' => 'Paris'
            ]
        ];

        /** ... Code omitted ... **/

        public function get($id)
        {
            return response()->json(
                $this->userCache[$id]
            );
        }

        /** ... Code omitted ... **/
    }

You can test this new method on Postman by doing a GET call to http://localhost:8084/api/v1/user/2; you should get something like this:

    {
        "name": "Joe",
        "city": "Paris"
    }

Once we know that the method to get the user information is working, we will call it from the Battle microservice. For security reasons, each container on Docker is isolated from the other containers, unless you specify that you want a connection in the links section of the docker-composer.yml file. To do so, use the following method:

  • Stop Docker containers:
          docker-compose stop
    
  • Edit docker-compose.yml file by adding the following line:
          microservice_battle_fpm:
              build: ./microservices/battle/php-fpm/
              volumes_from:
              - source_battle
              links:
                  - autodiscovery
                  - microservice_user_nginx
              expose:
                  - 9000
              environment:
                  - BACKEND=microservice-battle-nginx
                  - CONSUL=autodiscovery
  • Start Docker containers:
          docker-compose start
    

From now on, the Battle microservice should be able to see the User microservice, so let's call the User microservice in order to get the user information from the Battle microservice. To do this, we need to include the GuzzleHttpClient in the BattleController.php file and create a Guzzle instance in the duel function to use it:

    <?php
    namespace AppHttpControllers;
    use IlluminateHttpRequest;
    use AppAlgorithmDice;
    use GuzzleHttpClient;
    
    class BattleController extends Controller
    {
        const USER_ENDPOINT = 'http://microservice_user_nginx/api
        /v1/user/';
        /** ... Code omitted ... **/

        public function duel(Request $request)
        {
            $this->setBattleAlgorithm();
            $duelResult = $this->battleAlgorithm->fight();
            $client = new Client(['verify' => false]);
            $player1Data = $client->get(                          
            self::USER_ENDPOINT . $request->input('userA'));
            $player2Data = $client->get(                          
            self::USER_ENDPOINT . $request->input('userB'));
        
            return response()->json(
                [
                    'player1' => json_decode($player1Data->getBody()),
                    'player2' => json_decode($player2Data->getBody()),
                    'duelResults' => $duelResult
                ]
            );
        }
    }

Once we have finished the modifications, we can test it on Postman again by executing the same call as before--http://localhost:8081/api/v1/battle/duel (remember to make an HTTP POST call and send the parameters userA with value 1 and userB with value 2). The response should be similar to this (note that this time the user information is coming from the User microservice, although we are calling the Battle microservice):

    {
        "player1": {
            "name": "John",
            "city": "Barcelona"
        },
        "player2": {
            "name": "Joe",
            "city": "Paris"
        },
        "duelResults": {
            "player1": 0,
            "player2": 3
        }
    }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.39.142