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.