Как создать приложение Laravel с использованием архитектуры Lucid

В этой статье мы создадим приложение для ведения блога, используя ясную архитектуру — микроварианты.

Архитектура — это собрание связанных структур или искусство и процессы создания чего-либо. В контексте этого урока мы рассматриваем искусство и процессы создания приложения laravel.

Lucid принадлежит Vinelab, автором которой является @mulkave, а техническим руководителем является Abed Halawi — The Lucid Architecture for Building Scaleable Applications — Laracon EU 2016.

«Lucid — это программная архитектура для создания масштабируемых проектов Laravel.

Он включает в себя командную шину и проектирование, управляемое доменом, на основе которых он строит стек каталогов и классов для организации бизнес-логики. Он также происходит от SOA (сервисно-ориентированной архитектуры) — понятия инкапсуляции функциональности в сервисе и обогащает концепцию тем, что сервис представляет собой нечто большее, чем просто класс».

Используйте Lucid, чтобы:

  • Пишите чистый код без усилий
  • Защитите свой код от ухудшения с течением времени
  • Проверяйте код за доли времени, которое обычно требуется
  • Внедряйте проверенные методы и шаблоны в свои приложения
  • Навигация по коду и перемещение между кодовыми базами исходник

Эта архитектура представляет собой объединение лучших практик, шаблонов проектирования и проверенных методов, которые мы решили включить в наш код.

Встроенные шаблоны Lucid — это Command Bus и Domain Driven Design, на основе которых он строит стек каталогов и модулей для организации бизнес-логики. Он также происходит от SOA (сервисно-ориентированной архитектуры) понятия инкапсуляции функциональности в рамках «службы» и обогащает концепцию тем, что служба представляет собой нечто большее, чем просто класс.

  • Командная шина: для отправки единиц работы. В терминологии Lucid эти единицы будут функцией, работой или операцией.
  • Дизайн, ориентированный на предметную область: для организации единиц работы путем их классификации в соответствии с темой, к которой они принадлежат.
  • Сервис-ориентированная архитектура: для инкапсуляции и управления функциональными возможностями той же цели с их необходимыми ресурсами (маршруты, контроллеры, представления, миграции баз данных и т. д.)

Позиция

В типичном приложении MVC Lucid будет связующим звеном между точками входа приложения и модулями, выполняющими работу, защищая код от отклонений в резких направлениях:

Стек

Подробнее о Lucid Concept или The Lucid Architecture — Concept by Abed Halawi

Философия

Мы хотели что-то простое [KISS], но эффективное в больших масштабах. С ним приятно работать инженерам всех уровней. И повышает уровень понимания абстракции на протяжении всего процесса обучения. Проще говоря:

Lucid – это набор принципов, соблюдение которых зависит от нашей дисциплины

Подробнее о Lucid Philosophy

  • Функции должны служить одной цели
  • Джобс должен выполнять одну задачу
  • Домены не должны пересекаться
  • Функции не должны вызывать другие функции
  • Джобс не должен называть другие рабочие места
  • Операции не должны вызывать другие операции
  • Контроллеры обслуживают функции
  • Пишите код, понятный людям

Одноцелевой проект, содержащий умеренное количество функций, подходящих для аналогичного контекста.

Он содержит основные элементы стека Lucid: домены, функции, операции и данные, дополняя структуру Laravel:

Когда использовать Micro?

Micro подходит для большинства проектов, в том числе для быстрых прототипов, которые в какой-то момент должны стать реальными продуктами, и именно здесь Lucid вступает в дело, чтобы уменьшить технический долг, налагаемый с течением времени. Или проекты API, предназначенные для масштабирования.

Кроме того, как следует из названия, они лучше всего подходят для микросервисов, где у вас будет несколько экземпляров Laravel * Lucid Micro, каждый из которых представляет микросервис в вашей системе.

Установка

Lucid Architecture поставляется в виде пакета Composer lucidarch/lucid. Чтобы понять, как понятная микроархитектура используется для создания приложений laravel, мы создадим простое приложение для блога.

Начнем со свежей установки приложения laravel 9.

laravel new lucid_architecture_laravel_app 

Далее установите Lucid

composer require lucidarch/lucid

Обязательно поместите каталог bin поставщика проекта Composer в $PATH, чтобы исполняемый файл lucid мог быть найден вашей системой. Обычно это делается путем запуска export PATH=”$PATH:./vendor/bin”, чтобы сделать его доступным в текущем сеансе терминала, или добавить его в соответствующий профиль терминала (например, ~/.bashrc, ~/.bash_profile, ~ /.zshrc), чтобы он постоянно загружался при каждом сеансе.

export PATH="$PATH:./vendor/bin"

Далее инициализируйте экземпляр Micro:

Lucid Micro — вариант по умолчанию для одноцелевых приложений.

lucid init:micro

Это сгенерирует исходную микроструктуру:

Далее обслужить приложение

php artisan serve

Ясный стек

В попытке свести к минимуму работу контроллеров, поскольку они здесь не для того, чтобы выполнять работу для приложения, а для того, чтобы направить запрос в правильном направлении. В терминах Lucid, чтобы предоставить пользователю предполагаемую функцию.

В конце концов, мы получим по одной строке в каждом методе контроллера, достигая максимально возможной тонкой формы.

Ответственность: обслуживать назначенную функцию.

Представляет удобочитаемую функцию приложения в классе, названную так, как вы бы описали ее своим коллегам и клиентам. Он содержит логику реализации функции с минимальными трениями и уровнем детализации, чтобы оставаться кратким и прямолинейным.

Он запускает Lucid Units: Jobs and Operations для выполнения своих задач. Они рассматриваются как шаги в процессе служения своей цели. Функцию можно обслуживать из любого места, чаще всего из контроллеров и команд. Также можно поставить в очередь для асинхронного запуска с помощью мощных возможностей Laravel Queuing.

Технически это класс, который инкапсулирует все функции, необходимые для одного жизненного цикла запроса/ответа (или команды), в котором метод handle представляет список задач, когда вы хотите реализовать его в своем приложении.

Задания выполняют реальную работу, реализуя бизнес-логику. Будучи самой маленькой единицей в Lucid, Работа должна делать одну и только одну вещь — то есть: выполнять одну задачу. Это фрагменты кода, которые нам иногда хотелось бы иметь, и которые можно подключать в любом месте нашего приложения.

Наша цель с Jobs — ограничить объем одной функциональности, чтобы мы знали, где искать, когда находим ее, и когда мы находимся в ее контексте, мы не связываем ответственность с другими задачами, чтобы достичь окончательной формы принципа единой ответственности. .

Обычно вызывается функцией или операцией, но может вызываться из любого места любым другим классом после настройки в качестве пользовательского диспетчера; следовательно, это наиболее распространенные фрагменты кода.

Вдохновленный Domain-Driven Development, эта часть стека Lucid представляет собой просто каталоги, предназначенные для организации и категоризации кода в соответствии с темой, к которой они принадлежат.

Не существует определенного способа их именования, потому что он различается в каждом случае, но, согласно нашему многолетнему опыту, мы обнаружили, что они обычно бывают двух типов: «внутренний» и «внешний» тип домена. Вот несколько примеров, иллюстрирующих разницу

Запрос. Подобно Проверке формы Laravel, мы можем создать собственный запрос, используя понятные команды, чтобы они были организованы.

Из стека Lucid мы будем использовать функции, задания, домены и запросы для реализации следующего:

Затем установите переменную среды для этого приложения в файле .env

DB_CONNECTION=mysql 
DB_HOST=127.0.0.1 
DB_PORT=3306 
DB_DATABASE=lucid_architecture_laravel_app 
DB_USERNAME=root DB_PASSWORD=

Далее создайте таблицу сообщений

php artisan make:migration create_posts_table - -create=posts 

Добавьте следующие столбцы

$table->foreignId('user_id');
$table->string('title');
$table->text('description');

Далее запустите команду переноса

php artisan migrate

Затем с помощью команды lucid создайте PostController для управления публикациями и подготовьте его для обслуживания функций.

lucid make:controller Post

Найдите его в app/Http/Controllers/PostController.php

<?php
namespace App\Http\Controllers;
use Lucid\Units\Controller;
class PostController extends Controller
{}

Обратите внимание на автоматическое добавление суффикса Controller, используемого в качестве соглашения об именах для соответствия другим суффиксам в стеке Lucid, таким как Feature, Job и Operation.

Далее создайте модель Post с помощью команды lucid

lucid make:model Post

Обновите модель публикации

<?php
namespace App\Data\Models;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Post extends Model
{
    use HasFactory;
protected $fillable = [
        'title', 'description'
    ];
//setup the relationship with the users table
    public function user()
    {
//User model comes as default in laravel but you may wish to use the lucid command to create one
        return $this->belongsTo(User::class);
    }
//cast the created at attribute to be readable by humans
    public function created_at(): Attribute
    {
        return new Attribute(
            get: fn ($value) => $value->diffForHumans(),
        );
    }
}

Обновите модель пользователя

<?php
namespace App\Models;
//models created using lucid command are placed in the data folder
use App\Data\Models\Post;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
        'name',
        'email',
        'password',
    ];
protected $hidden = [
        'password',
        'remember_token',
    ];
protected $casts = [
        'email_verified_at' => 'datetime',
    ];
public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

Затем настройте маршруты для различных разделов приложения блога в файле route/web.php

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
Route::controller(PostController::class)->group(function () {
    Route::get('/', 'all_posts');
    Route::get('/home', 'index')->name('home');
});
Route::middleware(['auth', 'verified'])->group(function () {
    Route::resource('posts', PostController::class);
});

Далее создадим функцию с помощью команды lucid и вернем представление формы создания публикации

lucid make:feature CreatePostFeature

Класс объектов CreatePostFeature успешно создан.

public function create()
    {
        return $this->serve(CreatePostFeature::class);
    }

CreatePostFeature.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
class CreatePostFeature extends Feature
{
    public function handle()
    {
        return view('posts.create');
    }
}

Далее сохраните публикацию

lucid make:feature StorePostFeature

Класс объектов StorePostFeature успешно создан.

public function store()
    {
        return $this->serve(StorePostFeature::class);
    }

Создать проверку запроса для отправки формы

Первым шагом при получении входных данных является их проверка. Мы будем использовать проверку формы запроса, где каждый запрос принадлежит домену, представляющему управляемый объект, в данном случае это сообщение, содержащее класс запроса StorePost.

Это будет началом работы с доменами в Lucid. Они используются для группировки заданий и пользовательских классов, логика которых связана с определенными темами в соответствии с дизайном, ориентированным на предметную область.

Начиная с проверки, Lucid размещает классы запросов в соответствующих доменах. Сгенерируем запрос StorePost:

lucid make:request StorePost Post

Класс запроса успешно создан.

Найдите его в app\Domains\Post\Requests\StorePost.php.

В StorePost нам нужно обновить методы authorize() и rules(), которые создаются по умолчанию для проверки запроса и его ввода:

<?php
namespace App\Domains\Post\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePost extends FormRequest
{
    public function authorize()
    {
        return true;
    }
public function rules()
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'description' => ['required', 'string'],
        ];
    }
}

Теперь, когда наш запрос готов, нам нужно, чтобы StorePostFeature использовал этот класс запроса при обслуживании. Мы можем сделать это, просто внедрив класс запроса в метод handle() функции, и каждый раз, когда функция обслуживается, эта проверка будет применяться.

public function handle(StorePost $request)

Чтобы сохранить сообщение, мы создадим задание, которое сохраняет сообщения, и запустим его в нашей функции, которая будет добавлена ​​к нашему домену сообщений в app/Domains/Post/Jobs/SavePostJob.

lucid make:job SavePost Post

Класс задания SavePostJob успешно создан.

Найдите его в app\Domains\Post\Jobs\SavePostJob.php.

SavePostJob должен определять параметры, которые требуются в его конструкторе, т. е. сигнатуру задания, а не обращаться к данным из запроса, чтобы мы могли вызывать это задание из других мест в нашем приложении (например, из команды или пользовательского класса), а не быть ограниченным протоколом, в данном случае HTTP-запросом.

Мы используем эту технику, чтобы повысить степень изоляции работы и обеспечить принцип единой ответственности.

<?php
namespace App\Domains\Post\Jobs;
use Lucid\Units\Job;
use App\Data\Models\Post;
use Illuminate\Support\Facades\Auth;
class SavePostJob extends Job
{
public function __construct(public $title, public $description)
    {}
public function handle()
    {
        $attributes = [
            'title' => $this->title,
            'description' => $this->description,
        ];
$post = new Post($attributes);
        $user = Auth::user();
return $user->posts()->save($post);
    }
}

Затем мы запустим это задание из функции, чтобы сохранить сообщения при получении:

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Domains\Post\Jobs\SavePostJob;
use App\Domains\Post\Requests\StorePost;
class StorePostFeature extends Feature
{
    public function handle(StorePost $request)
    {
        $this->run(SavePostJob::class, [
            'title' => $request->input('title'),
            'description' => $request->input('description'),
        ]);
    }
}

Вызов $this-›run($unit, $params) в функции заставляет базовый диспетчер запускать SavePostJob синхронно, вызывая его метод дескриптора после его инициализации с предоставленными $params, которые передаются как ассоциативный массив, где ключи должны соответствовать параметры конструктора задания в именовании, но не в порядке. Так что это все равно будет работать так же:

$this->run(SavePostJob::class, [
            'description' => $request->input('description'),
            'title' => $request->input('title'),
        ]);

Следующий шаг — перенаправить успешный запрос обратно в форму создания. Для этого мы создадим RedirectBackJob, который просто вызовет функцию back(). Хотя это может показаться накладным, но помните, что мы настраиваемся на масштабирование, и по мере масштабирования чем меньше у нас свободного кода, тем лучше; вместо большого количества вызовов back() и back()-›withInput() и других, мы централизуем их в задании, так что в случае, если мы когда-нибудь захотим изменить эту функциональность или добавить к ней, изменение должно будет произойти только в одном месте.

RedirectBackJob будет находиться в новом домене Http, месте для всех наших функций, связанных с HTTP, которые не относятся к бизнес-сущности нашего приложения, соответствуют абстрактному типу доменов, а не типу сущности.

lucid make:job RedirectBackJob http

Класс задания RedirectBackJob успешно создан.

Найдите его в app\Domains\Http\Jobs\RedirectBackJob.php.

Наше задание предоставит параметр withMessage, чтобы определить, следует ли включать сообщение в перенаправление. Это простой пример того, как такое простое задание может впоследствии предоставлять функциональные возможности, которые можно использовать в приложении.

<?php
namespace App\Domains\Http\Jobs;
use Lucid\Units\Job;
class RedirectBackJob extends Job
{
//PHP 8 constructor
    public function __construct(private $withMessage){}
/**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $back = back();
if ($this->withMessage) {
            //custom success message
            $back->with('success', $this->withMessage);
        }
        return $back;
    }
}

Далее обновим StorePostFeature, чтобы использовать задание перенаправления после создания публикации

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Domains\Post\Jobs\SavePostJob;
use App\Domains\Post\Requests\StorePost;
use App\Domains\Http\Jobs\RedirectBackJob;
class StorePostFeature extends Feature
{
    public function handle(StorePost $request)
    {
        $this->run(SavePostJob::class, [
            'title' => $request->input('title'),
            'description' => $request->input('description'),
        ]);
return $this->run(RedirectBackJob::class, [
            'withMessage' => 'Post created successfully.',
        ]);
}
}

Затем создайте метод для обработки отображения всех сообщений пользователей, назовем его all_posts:

public function all_posts(){
        return $this->serve(AllPostsFeature::class);
    }

Создайте AllPostsFeature, который будет обслуживаться методом all_posts для возврата всех сообщений

lucid make:feature AllPostsFeature

AllPostsFeature.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
class AllPostsFeature extends Feature
{
//The handle method is automatically created by the Lucid command which we'll use to return all the posts
public function handle()
    {
            $posts = Post::latest()->get();
return view('home', compact('posts'));
}
}

Обновите метод all_posts в PostController

public function all_posts(){
        return $this->serve(AllPostsFeature::class);
    }

Затем создайте метод index для обработки списка сообщений, созданных пользователем:

public function index()
    {}

Затем метод index будет обслуживать функцию, которая будет запускать задания, необходимые для отображения сообщений. Создайте функцию под названием PostsIndexFeature:

lucid make:feature PostsIndexFeature

Класс объектов PostsIndexFeature успешно создан.

Найдите его в app\Features\PostsIndexFeature.php.

Далее обновите метод index, чтобы он обслуживал функцию индексации записей

<?php
namespace App\Http\Controllers;
use Lucid\Units\Controller;
use App\Features\PostsIndexFeature;
class PostController extends Controller
{
    public function index(){
        return $this->serve(PostsIndexFeature::class);
    }
}

Метод handle в PostsIndexFeature автоматически создается командой Lucid, которую мы будем использовать для возврата всех сообщений

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
use Illuminate\Support\Facades\Auth;
class PostsIndexFeature extends Feature
{
    public function handle()
    {
$posts = Post::where('user_id', Auth::user()->id)->latest()->get();
return view('posts.index', compact('posts'));
    }
}

Тот же подход к использованию функций, заданий и запросов, упомянутый выше, используется в следующих разделах:

Показать раздел сообщения:

lucid make:feature ShowPostFeature

Класс объектов ShowPostFeature успешно создан.

Найдите его в app\Features\ShowPostFeature.php.

Обновите ShowPostFeature.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
class ShowPostFeature extends Feature
{
    public function __construct(
        public Post $post,
    ) {
    }
public function handle()
    {
        return view('posts.show')->with('post', $this->post);
    }
}

Показать метод в пост-контроллере

public function show(Post $post)
    {
        return $this->serve(ShowPostFeature::class, [
            'post' => $post,
        ]);
    }

Раздел «Редактирование и обновление публикации»:

lucid make:feature EditPostFeature

Класс пространственных объектов EditPostFeature успешно создан.

Найдите его в app\Features\EditPostFeature.php.

Обновите EditPostFeature.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
class EditPostFeature extends Feature
{
    public function __construct(
        public Post $post,
    ) {
    }
public function handle()
    {
        return view('posts.edit')->with('post', $this->post);;
    }
}

Метод редактирования в PostController

public function edit(Post $post)
    {
        return $this->serve(EditPostFeature::class, [
            'post' => $post,
        ]);
    }

Метод обновления в PostController

lucid make:feature UpdatePostFeature

Класс пространственных объектов UpdatePostFeature успешно создан.

Найдите его в app\Features\UpdatePostFeature.php.

Создать задание UpdatePost

lucid make:job UpdatePost Post

Класс задания UpdatePostJob успешно создан.

Найдите его в app\Domains\Post\Jobs\UpdatePostJob.php.

UpdatePostJob.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
use App\Domains\Post\Jobs\UpdatePostJob;
use App\Domains\Post\Requests\StorePost;
use App\Domains\Http\Jobs\RedirectBackJob;
class UpdatePostFeature extends Feature
{
    public function __construct(
        public Post $post,
    ) {
    }
public function handle(StorePost $request)
    {
$this->run(UpdatePostJob::class, [
            'post' => $this->post,
            'title' => $request->input('title'),
            'description' => $request->input('description'),
        ]);
return $this->run(RedirectBackJob::class, [
            'withMessage' => 'Post updated successfully.',
        ]);
    }
}

Обновите UpdatePostFeature.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
use App\Domains\Post\Jobs\UpdatePostJob;
use App\Domains\Post\Requests\StorePost;
use App\Domains\Http\Jobs\RedirectBackJob;
class UpdatePostFeature extends Feature
{
    public function __construct(
        public Post $post,
    ) {
    }
public function handle(StorePost $request)
    {
$this->run(UpdatePostJob::class, [
            'post' => $this->post,
            'title' => $request->input('title'),
            'description' => $request->input('description'),
        ]);
return $this->run(RedirectBackJob::class, [
            'withMessage' => 'Post updated successfully.',
        ]);
    }
}

метод обновления в PostController

public function update(Post $post)
    {
        return $this->serve(UpdatePostFeature::class, [
            'post' => $post,
        ]);
    }

Удалить раздел сообщения:

Класс объектов DeletePostFeature успешно создан.

Найдите его в app\Features\DeletePostFeature.php.

Обновите файл DeletePostFeature.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
class DeletePostFeature extends Feature
{
    public function __construct(
        public Post $post,
    ) {
    }
public function handle()
    {
$this->post->delete();
return redirect()->route('posts.create')
        ->with('success', 'Post deleted!!!');
    }
}

Чтобы удалить запись, воспользуемся методом destroy

Обратите внимание, что для производственных приложений необходимо реализовать мягкое удаление Laravel.

public function destroy(Post $post)
    {
        return $this->serve(DeletePostFeature::class, [
            'post' => $post,
        ]);
    }

Обновите DeletePostFeature.php

<?php
namespace App\Features;
use Lucid\Units\Feature;
use App\Data\Models\Post;
class DeletePostFeature extends Feature
{
    public function __construct(
        public Post $post,
    ) {
    }
public function handle()
    {
$this->post->delete();
return redirect()->route('posts.create')
        ->with('success', 'Post deleted!!!');
    }
}

Очень круто!!! это все, для получения дополнительной информации о Lucid Architecture проверьте их документацию

Давайте обслуживать приложение и использовать его

Создать запись

Все записи

Показать сообщение с функцией редактирования и удаления

Учебный репозиторий — филиал Lucid-Micro Исследовательских кредитов Подробнее об архитектуре Lucid Документация Lucid Абед Халави — ясная архитектура для создания масштабируемых приложений — Laracon EU 2016

Спасибо, что прочитали эту статью.

Пожалуйста, поделитесь со своей сетью и не стесняйтесь использовать раздел комментариев для вопросов, ответов и предложений.

Первоначально опубликовано на https://alemsbaja.hashnode.dev.