Anthony Beckett

Software Engineer, Videographer and Web Designer

A Lumen PHP Microservice Tutorial

In this tutorial we are going to create a few lumen php applications that will constitute a micro-service architecture.

Installing lumen and running the service

To begin building a micro-service with Lumen install the base lumen components open up a terminal window (I use iTerm) and navigate to the directory that you want to use to contain each micro service.

Note: each micro service will have its own root directory; in local development I will keep them all in the directory Development/Lumen/Microservices/ on my iMac.

If you are using Windows I recommend using Powershell but and remember that windows uses the backslash \ for directory separator unlike the examples below that use forward-slash / as the directory separator (used by Mac / Linux / Unix).

First we will build our LumenUsersAPI micro-service.

You will need composer (php package manager) for this step:

anthony$ cd Development/Lumen/Microservices/
anthony$ composer create-project laravel/lumen LumenUsersApi 6.3.*
Installing laravel/lumen (v6.3.4)
  - Installing laravel/lumen (v6.3.4): Downloading (100%)
Created project in LumenUsersApi
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
. . .  

Now navigate to the root directory of your new micro-service. I use a new tab in my terminal for this. We will be creating more services in the original location later on. For now, let’s navigate to the root of our new micro service and get the service running.

You will need php installed on your development environment to follow this step:

anthony$ ls
LumenUsersApi
anthony$ cd LumenUsersApi/
anthony$ php -S localhost:8000 -t ./public/
PHP 7.3.6 Development Server started at Thu Feb 27 10:46:48 2020
Listening on http://localhost:8000
Document root is /Users/Anthony/Development/Lumen/Microservices/LumenUsersApi/public
Press Ctrl-C to quit.

Using Postman or a browser go to http://localhost:8000 and confirm the application is running.

You should see a response like this confirming that the service is running:

Lumen (5.7.8) (Laravel Components 5.7.*)

Configure Some Required Lumen Components

We now need to switch on some required lumen components for our application.

Open ./bootstrap/app.php and uncomment the lines:

//$app->withFacades();

//$app->withEloquent();

Giving:

$app->withFacades();

$app->withEloquent();

Facades makes available some helper methods and some useful global functions and Eloquent allows us to read and delete entries in the database.

Setting UP Environment Variables and the database

We now need to add some details to our dot env file.

Open the file ./.env and change the database to sqlite for this tutorial. Of course you are free to set up MySQL, Postgres or even DynamoDB is you like. In this examples we will keep it simple and use sqlite.

DB_CONNECTION=sqlite

Then comment out the other database references:

DB_CONNECTION=sqlite
#DB_HOST=127.0.0.1
#DB_PORT=3306
#DB_DATABASE=homestead
#DB_USERNAME=homestead
#DB_PASSWORD=secret

Sqlite stores data in a single file that has to be in a specific directory. Create a file ./database/database.sqlite. This will be set up and managed by Lumen. We don’t need to do more with it for the purpose of getting started with the tutorial.

However, we should add the file to our .gitignore just to be sure we don’t push it to our git repository later on.

Finally, for our environment setup, we also need to create an APP KEY. Create a random key and set it in the env file:

APP_KEY=jd78UkmLJ259ew9mAijwKMmluY58VbpP

The service is now set up and ready for coding.

But first we will build a users table and corresponding model for use by our LumenUsersAPI service.

Creating the database table and the model

We will use Lumen/Laravel’s artisan command to create migrations to build our users table.

Running php artisan gives us a list of available commands:

anthony$ php artisan
Laravel Framework Lumen (5.7.8) (Laravel Components 5.7.*)

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  help                Displays help for a command
  list                Lists commands
  migrate             Run the database migrations
 auth
  auth:clear-resets   Flush expired password reset tokens
 cache
  cache:clear         Flush the application cache
  cache:forget        Remove an item from the cache
  cache:table         Create a migration for the cache database table
 db
  db:seed             Seed the database with records
 make
  make:migration      Create a new migration file
  make:seeder         Create a new seeder class
 migrate
  migrate:fresh       Drop all tables and re-run all migrations
  migrate:install     Create the migration repository
  migrate:refresh     Reset and re-run all migrations
  migrate:reset       Rollback all database migrations
  migrate:rollback    Rollback the last database migration
  migrate:status      Show the status of each migration
 queue
  queue:failed        List all of the failed queue jobs
  queue:failed-table  Create a migration for the failed queue jobs database table
  queue:flush         Flush all of the failed queue jobs
  queue:forget        Delete a failed queue job
  queue:listen        Listen to a given queue
  queue:restart       Restart queue worker daemons after their current job
  queue:retry         Retry a failed queue job
  queue:table         Create a migration for the queue jobs database table
  queue:work          Start processing jobs on the queue as a daemon
 schedule
  schedule:run        Run the scheduled commands

This shows you all your available commands and any custom command you may choose to make in future.

For our current task we will use the make:migration command to create our database table:

anthony$ php artisan make:migration CreateUsersTable --create=users
Created Migration: 2019_12_27_131830_create_users_table

This has now created a file in your applications migration directory i.e. ./database/migrations/2019_12_27_131830_create_users_table.php

This file is a php class with the class name you specified when creating the migration CreateUsersTable that extends the functionality of the existing Illuminate component migration class. Your new migration class defines only an CreateUsersTable::up() method and a CreateUsersTable::down() method for creating the table and destroying the table respectively.

The table structure we will define in the CreateUsersTable::up() method as follows. Change:

        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });

To:

        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name'); // We added a new field
            $table->timestamps();
        });

Now we run the migration to create the table:

anthony$ php artisan migrate
Migration table created successfully.
Migrating: 2019_12_27_131830_create_users_table
Migrated:  2019_12_27_131830_create_users_table
anthony$

We now need a model for mapping this for to the Lumen framework.

Lumen has already created a Model for us when we installed it and this just happens to be called ./app/User.php:

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Laravel\Lumen\Auth\Authorizable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;

class User extends Model implements AuthenticatableContract, AuthorizableContract
{
    use Authenticatable, Authorizable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email',
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];
}

Had we chosen a different entity / table name other than User / Users then we could simply modify this class and rename the class to the singular for the entity name that you chose. Since we have a collections of Users in a table and each entity is a User then this class name is suffice.

We will however remove the interfaces that the Model class extends for this tutorial. We will also edit the file to set the field we added to the migration as to the User::fillable attribute (and remove the unwanted columns) for our database table structure giving:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
	/**
	 * The attributes that are mass assignable.
	 *
	 * @var array
	 */
	protected $fillable = [
		'name' // this is the field we added
	];
}

Next we will create our User Factory. Lumen was installed with a User factory already defined in ./database/factories/ModelFactory.php. In this file we just need to give the return value of the annoymous function array keys that match our database table field names.

Change:

$factory->define(App\User::class, function (Faker\Generator $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email, // We remove this one
    ];
});

To:

$factory->define(App\User::class, function (Faker\Generator $faker) {
    return [
        'name' => $faker->name,
    ];
});

To generate the fake fixture data in our database we add our factory to the database seeder class. Change:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        // $this->call('UsersTableSeeder');
    }
}

To:

<?php

use App\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
	/**
	 * Run the database seeds.
	 *
	 * @return void
	 */
	public function run() {
		factory(User::class, 10)
			->create();
	}
}

Now when we run our seeder we will get a small batch of users inserted into our database with random values:

anthony$ php artisan db:seed
Database seeding completed successfully.

You can also reprovision your database with the following command that will drop tables, recreate them and repopulate them:

anthony$ php artisan migrate:fresh --seed
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2020_02_27_131830_create_users_table
Migrated:  2020_02_27_131830_create_users_table

This is useful if you are debugging issues with your database configurations or making changes to your structure.

Next we will create a controllers class where business logic normally resides.

Creating a Controller

To create a controller file you can duplicate the file ./app/Http/Controllers/ExampleController.php and create ./app/Http/Controllers/UserController.php.

Change the class name to UserController and add class methods:

	/**
	 * Return a list of users
	 *
	 * @return Illuminate\Http\Response
	 */
	public function index() {
            return App\User::all();
	}

	/**
	 * Create an new user
	 *
	 * @return Illuminate\Http\Response
	 */
	public function create(Request $user) {
	}

	/**
	 * Show details of a user
	 *
	 * @return Illuminate\Http\Response
	 */
	public function read($user) {
	}

	/**
	 * Update a user
	 *
	 * @return Illuminate\Http\Response
	 */
	public function update(Request $request, $user) {
	}

	/**
	 * Delete a user
	 *
	 * @return Illuminate\Http\Response
	 */
	public function delete($user)
	{
	}

Add our routes for CRUD operations

CRUD (create, read, update, delete) operations for this service require that we set up the routes or endpoints for those operations.

Open ./routes/web.php and change:

$router->get('/', function () use ($router) {
    return $router->app->version();
});

To:

// CRUD endpoints
$router->get('/users', '[email protected]');
$router->post('/users', '[email protected]');
$router->get('/users/{user}', '[email protected]');
$router->put('/users/{user}', '[email protected]');
$router->patch('/users/{user}', '[email protected]');
$router->delete('/users/{user}', '[email protected]');

Patch here is really just an alias for put (update) http method.

At this point we now have our routes set up and therefore a simple get request on /users should call the method index on the class UserController.

Using a browser or postman give that a try. You should see the faked fixture data you generated earlier in the response.

Go ahead and give it a try.

[To Do: add the missing code for the other UserController methods]

Adding a second microservice

We repeat the process used for LumenUsersAPI for an additional service. Here our second service will be called LumenScoresAPI.

anthony$ cd Development/Lumen/Microservices/
anthony$ composer create-project laravel/lumen LumenScoresApi
Installing laravel/lumen (v6.3.4)
  - Installing laravel/lumen (v6.3.4): Downloading (100%)
Created project in LumenScoresApi
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
. . .  
anthony$ ls
LumenScoresApi
anthony$ cd LumenScoresApi/
anthony$ php -S localhost:8001 -t ./public/
PHP 7.3.6 Development Server started at Thu Feb 27 10:46:48 2020
Listening on http://localhost:8001
Document root is /Users/Anthony/Development/Lumen/Microservices/LumenScoresApi/public
Press Ctrl-C to quit.

[To do: complete the second service setup]

Setting up your API Gateway

The Api Gateway is itself a micro-service built with lumen. Let’s navigate to the folder that will contain all three micro-services (just for convenience) and create it:

anthony$ cd Development/Lumen/Microservices/
anthony$ composer create-project laravel/lumen LumenApiGateway
Installing laravel/lumen (v6.3.4)
  - Installing laravel/lumen (v6.3.4): Loading from cache
Created project in LumenApiGateway
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
anthony$ ls
LumenApiGateway LumenScoresApi LumenUsersApi
(base) Anthonys-iMac:Microservices anthony$ cd LumenApiGateway/
(base) Anthonys-iMac:LumenApiGateway anthony$ php -S localhost:8002 -t ./public/
PHP 7.3.6 Development Server started at Fri Feb 28 13:06:07 2020
Listening on http://localhost:8002
Document root is /Users/anthony/Development/Lumen/Microservices/LumenApiGateway/public
Press Ctrl-C to quit.

Unlike previous services we won’t be creating any models or factories as it uses the other two services for data and simply makes requests to them.

We’ll be using Guzzle to communicate with the other services and so we need to require that package.

anthony$ composer require guzzlehttp/guzzle
Using version ^6.5 for guzzlehttp/guzzle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
. . . 

Configuring the services

We create a file ./config/services.php.

Next Post

Leave a Reply

© 2020 Anthony Beckett

Theme by Anders Norén