Build a domain-oriented Api on Laravel Framework
MIT License
This package builds a structure to domain-oriented APIs (not DDD, they are different things). With search filters, validations and clean code.
My need was simple: build structures in an organized and productive way. A structure that supports filters, validations and data caching (CQRS).
Before proceeding, take a look at the final structure:
app
├── ...
├── Domain
│ └── Dummy
│ ├── DummyFilterService.php
│ ├── DummyPersistenceModel.php
│ ├── DummyPersistenceService.php
│ ├── DummyPolicy.php
│ ├── DummyResource.php
│ ├── DummySearchModel.php
│ ├── DummySearchService.php
│ └── DummyValidateService.php
├── Http
│ ├── Controllers
│ │ ├── ...
│ │ └── DummyController.php
├── ...
database
├── factories
│ └── ...
│ └── DummyFactory.php
├── migrations
│ ├── ...
│ └── 2021_01_06_193044_create_dummies_table.php
└── seeders
├── DatabaseSeeder.php
└── DummySeeder.php
You must be asking yourself:
$ composer require adhenrique/laravel-domain-oriented
php artisan vendor:publish --provider="LaravelDomainOriented\ServiceProvider" --tag="lang"
$ php artisan domain:create Dummy
--force
flag:$ php artisan domain:create Dummy --force
$ php artisan domain:remove Dummy
That's it enjoy!
Our Model's follow the Eloquent Model Conventions
Our Migrations follow the Laravel Migration Structure
Here, too, we follow the Laravel way of doing things:
Again, Policies follow the Laravel Policy Authorization
Note: You don't have to worry about registering your policies, as we do it behind the scenes. However, here we follow a class name convention. When creating a domain, your class must be named SomethingPolicy and belong to the App\Domain\Something namespace.
ValidateService is located at app/Domain/YourDomainName/*
:
use LaravelDomainOriented\Services\ValidateService;
class DummyValidateService extends ValidateService
{
protected array $rules = [
// You can define general validation rules, which will be inherited
// for all actions, or you can define validation rules for each action:
// SHOW, STORE, UPDATE, DESTROY
// General rules validation.
// If any action validation rule is not defined, it will inherit from here.
'name' => 'required|string',
// Specific action rules validation. If set, ignores general validations.
self::SHOW => [
'id' => 'required|integer',
],
self::UPDATE => [
'id' => 'required|integer',
'name' => 'required|string',
],
self::DESTROY => [
'id' => 'required|integer',
],
];
}
We follow Laravel routes pattern. But as we are dealing with API, modify the file routes/api.php
, adding the following routes:
Route::get('dummies', 'App\Http\Controllers\DummyController@index');
Route::get('dummies/{id}', 'App\Http\Controllers\DummyController@show');
Route::post('dummies', 'App\Http\Controllers\DummyController@store');
Route::put('dummies/{id}', 'App\Http\Controllers\DummyController@update');
Route::delete('dummies/{id}', 'App\Http\Controllers\DummyController@destroy');
In the SearchService class you have two methods that help you to pre-start queries according to your needs: beforeAll
and beforeFindById
.
Each method receives 2 parameters: builder
with the Eloquent instance started and auth
, with the user session - if are logged in.
You just need to override the methods, but ensure that the return is eloquent's Builder
. Look:
class DummySearchService extends SearchService
{
protected SearchModel $model;
protected FilterService $filterService;
public function __construct(DummySearchModel $model, DummyFilterService $filterService)
{
$this->model = $model;
$this->filterService = $filterService;
}
public function beforeAll(Builder $builder, Guard $auth): Builder
{
return $builder;
}
public function beforeFindById(Builder $builder, Guard $auth): Builder
{
return $builder;
}
}
In my use case, logged in as admin, I usually filter from the list of users my own user. Look:
// ...
public function beforeAll(Builder $builder, Guard $auth): Builder
{
return $this->removeLoggedFromSearches($builder, $auth);
}
private function removeLoggedFromSearches($builder, $auth)
{
$id = $auth->id();
return $builder->where('id', '<>', $id);
}
You can filter and paginate the data on the listing routes. To do this, send a payload on the request, using your favorite client:
Simple Where:
{
"name": "adhenrique",
"email": "[email protected]"
}
Where in:
{
"id": [1,2,3]
}
Where by operator (like, >, =>, <, <=, <>):
{
"name": {
"operator": "like",
"value": "%adhenrique%"
}
}
Where between:
{
"birthdate": {
"start": "1988-13-12",
"end": "2021-01-01"
}
}
Paginate results
{
"paginate": {
"per_page": 1,
"page": 1
}
}
Note: You can use the filters and pagination together.
$ composer test
Please see CHANGELOG for more information what has changed recently.
Please see CONTRIBUTING for details.
If you discover any security related issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.
[1] Please, stop talking about Repository pattern with Eloquent [2] Useful Eloquent Repositories? [3] Você entende Repository Pattern? Você está certo disso? Laravel — Why you’ve been using the Repository Pattern the wrong way