Part I

1. Create Api Service

We will create a service called Api. This service, obviously allows Api access to our application. Another example of a service would be Admin where the application can be managed through an administration interface, or Web for the website of this application.

lucid make:service Api

Register Api

  • Open src/Foundation/Providers/ServiceProvider

  • Add use App\Services\Api\Providers\ApiServiceProvider to the top

  • In the register method add $this->app->register(ApiServiceProvider::class)

Now our project recognises Api service, so we can add Features and Jobs to get things moving.

The Api directory will initially contain the following:

src/Services/Api
├── Console         # Everything that has to do with the Console (i.e. Commands)
├── Features        # Contains the Api's Features classes
├── Http            # Routes, controllers and middlewares
├── Providers       # Service providers and binding
├── database        # Database migrations and seeders
└── resources       # Assets, Lang and Views

2. CreateArticleFeature

Using the CLI's make:feature {feature} {service} we will create our first Feature

CreateArticle will eventually be created as CreateArticleFetaure. Even if you had entered CreateArticleFeature or createArticleFeaure it would still work. However, middle words are case-sensitive, so createarticle would have ended up CreatearticleFeature.

Two new files have been created with this command:

src/Services/Api/Features/CreateArticleFeature.php and src/Services/Api/Tests/CreateArticleFeatureTest.php

  • Open src/Services/Api/Features/CreateArticleFeature.php file to fill the handle method with the steps that we are about to run. This method is executed automatically when calling serve(CreateArticleFeature::class) inside the controller as we will see later.

Each of these comments inside the handle method means that we need to create a job to perform its task.

2.1 ValidateArticleInputJob

Our first job is to validate the input. As promised, Lucid will provide the clarity of reading your task list when overlooking the Feature's handle class. For that reason, our job's class will be called exactly what needs to be done ValidateArticleInputJob, to be create using the command make:job with the following signature:

Our job will live inside the Article domain, which will contain everything related to managing articles:

This will generate two files:

src/Domains/Article/Jobs/ValidateArticleInputJob.php and tests/Domains/Article/Jobs/ValidateArticleInputJobTest.php

Let's examine our job class:

The __construct defines the signature of the job, it defines what the job requires to be run. handle is the method that is automatically called when the feature calls $this->run(ValidateArticleInputJob::class)

Fill Validation Job

Let's retrieve the input and validate it here. The construct will receive the input as received from the request and will pass it to the validator which will throw an exception if it doesn't qualify, and return true if it does.

For the simplicity of this example we chose to add the generic validator and perform the validation in the job itself, however, a better approach would be to create a validator [or more] per domain.

PHPDoc Blocks were eliminated for brevity, but they are recommended to be kept and updated, as they do exist in the code shared on GitHub.

The handle method supports IoC so we can inject classes and have them resolved. We are also using Lucid's Validator class for easy validation.

Test Validation Job

We love tests, for that reason it is recommended at this stage to write a test to ensure that this job does exactly what is intended in tests/Domains/Article/Jobs/ValidateArticleInputJobTest.php

Test Validation Success Scenario

As an initial test, let's ensure that all goes well within our job:

Running this test should go all green. Now let's test our validation failure cases using a data provider

Test Validation Failure Scenarios

Using Lucid's Validator the exception that will be thrown is \Lucid\Foundation\InvalidInputException which is surely customisable by simply extending the Validator class with your own and providing a custom exception to be thrown which is explained in depth in [..... LINK TO VALIDATOR EXPLANATION ....].

The test will look as follows:

ℹ️

There sure are better ways to deal with data providers, but for the simplicity of this example it was decided to use them as such.

2.2 SaveArticleJob

Our next task is to store the article information in the database. Simple.

Generate SaveArticleJob in our article domain

Before we start filling our job, we need to ensure that the Article object exists. For the sake of simplicity, we will not be using the Article object that is shipped with Laravel, but we will create our own with the lucid make:model command:

A new Article class has been created under src/Data/Article.php, let's fill it:

Prerequisites

Before continuing with this example we need to configure a database to save our article into. For this example we will use sqlite for we will only be running our code through tests.

  • Configure PHPUnit to use SQLite by adding the following to the bottom of phpunit.xml file under the <php> tag:

  • Generate migration to create articles table

Like other Lucid components, migrations are also service-specific. More on this in the Data section [coming later]. This will generate a new file in src/Services/api/database/migrations

Save the Article

In our job src/Domains/Article/Jobs/SaveArticleJob.php we read the values required for this task and simply create an Article Eloquent instance and save it.

💭

You might be thinking why we didn't create a "CreateArticleJob" instead of this. The answer to this is reusability, for later on we will need to probably update an article or expand on the functionality of saving an article into the database, so this job's functionality will be reused.

Another question that comes to mind here is: why do we receive title and content and not just pass the input as received from the request as an array, or even the Request itself, which can even be injected in handle?

And its tests to ensure it works:

Note the included traits DatabaseMigrations and RefreshDatabase.

2.3 Serving Features

Back to our Feature class CreateArticleFeature , now that we are sure that our jobs will run as expected and return the expected results, and given that we provide the correct input, we now need to run them in the handle method of our Feature:

Note: RespondWithJsonJob is a built-in job so no need to do anything additional to have it.

Our jobs have replaced the comments that was previously present here because it would be redundant to be kept; this highlights the importance in naming your jobs properly and being as expressive as you would in the comment that would've replaced it to explain its role.

Let's examine the Feature above before we head to its tests.

A Glance at the Feature

At a glance, you can tell the following:

  • The steps that are required to accomplish this feature

  • What each of the steps require as input in order to run

  • Not much clutter, just enough information to be introduced to the feature

Feature → Run(Job) Syntax

Mark the syntax of running the validation job:

This syntax enables us to pass parameters interchangeably, without caring about the order in which they are defined within the Job's signature. For example, the following would still work:

This grants us the isolation required for each scope:

  • At the Job level, I am free to change the signature as I see fit

  • At the Feature level, I am only concerned with the required parameters, not their order

Testing the Feature

To ensure the validity of the above, we sure have to write a test to execute our Feature.

Testing Features is different from that of Jobs and Operations. It is a functional test that ensures the integration and integrity of all the Jobs and Operations that are run by the feature and it is done using Laravel's HTTP tests. The test file for our Feature has already been created so we just need to fill it:

This will ensure that the Feature works, but it it's sufficient for us to sleep in peace at night, because it doesn't cover error cases to ensure that the client of our Api service will receive an informative error response in JSON format. We will expand on this in Part III of this tutorial.

Last updated

Was this helpful?