Part I
You are looking at the depricated version of the docs. See https://docs.lucidarch.dev for the latest documentation.
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.
Register Api
Open
src/Foundation/Providers/ServiceProvider
Add
use App\Services\Api\Providers\ApiServiceProvider
to the topIn 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:
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 thehandle
method with the steps that we are about to run. This method is executed automatically when callingserve(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