В данной статье мы с вами узнаем
- почему не стоит использовать модели в контроллерах
- изучим концепцию инфраструктурного слоя (уровень доступа к данным)
- изучим шаблон проектирования Репозиторий
- и немного о том, как можно писать интеграционные тесты бд
- и, наконец, познакомимся с принципом инверсии зависимости в SOLID
Каковы недостатки использования запросов через Eloquent модели или напрямую к бд в ваших контроллерах Laravel?
Есть несколько причин, например, вы нарушаете SOLID принципы:
- Принцип единой ответственности – ваши контроллеры несут дополнительную ответственность за управление базой данных
- Принцип инверсии зависимостей – ваши контроллеры беспокоятся о том, как сохранить данные, вместо того, чтобы знать, когда их сохранить.
Также есть пара других принципов, таких как:
- DRY — как только вы добавите больше контроллеров / воркеров очереди / команд, вы будете копировать — вставлять одну и ту же логику везде
В общем, раздувая ваши контроллеры, вы делаете вашу систему более сложной в обслуживании (из-за всей этой копипасты), она не является гибкой (вы не можете просто рефакторировать, чтобы использовать другой сохраняемый драйвер) и спагетти-код (потому что вы смешиваете ссылки на слои).
Значит ли это, что вы никогда не должны этого делать? ну нет… если вы опытный разработчик и ваша единственная цель – создать прототип чего-то быстрого (например, эксперимент), то не следовать всему этому было бы довольно удобно. Может быть, вам просто нужно запустить сервис, который только временно собирает данные, и есть стремительно приближающийся дедлайн. Тогда не стоит прибегать к этим принципам.
Но помните: нет ничего более постоянного, чем временное. Просто знайте о техническом долге и решайте его, как только эксперимент окажется эффективным.
Инфраструктурный уровень в Laravel
Это место, где вы должны разместить всю свою логику инфраструктуры. Что это за логика? Это все, что связано с внешним миром, например: другие сервисы, файловая система, база данных и все, чем ваше приложение не “владеет”.
Вы должны написать свой код так, чтобы он абстрагировался от этих средств “связи” с внешним миром. Поэтому поместите все ваши eloquent модели и запросы к базе данных в этот слой.
Для этого мне нравится создавать еще одну папку в моем приложении (или src) под названием Infrastructure, где я размещаю все реализации своих запросов к БД.
Шаблон проектирования Репозиторий
Я видел несколько способов написания шаблона репозитория, и распространенной ошибкой является представление репозитория эквивалентом соединения с базой данных. То, что действительно должно представлять собой коллекцию да, это верно, подумайте об абстракции коллекции, чтобы верхнему слою не пришлось беспокоиться ни о том “как“, ни о том “где” хранятся данные. Каковы общие методы репозитория?
Давайте начнем с основных:
<?php declare(strict_types=1);
namespace App\Users\Adapters\Contracts;
use App\User;
use Illuminate\Support\Collection;
interface UserRepository
{
public function add(User $user): void;
public function remove(User $user): void;
public function findAll(): Collection;
public function findOneById(int $userId): User;
}
Конечно, это только один из многих способов сделать это. Вот еще хороший текст про репозитории в Laravel
Где находится интеграционное тестирование базы данных в пирамиде тестирования?
Теперь давайте сосредоточимся на интеграционном тестировании, именно здесь вы должны протестировать все свои классы, которые отвечают за связь с внешним миром. Вот несколько правил:
- Не используйте mock/stub для коннекторов и драйверов, просто дайте им работать. Позвольте им сделать реальное соединение и вернуть результат. Только так вы будете знать, что ваша интеграция работает правильно.
- Вы можете мокнуть сервис, к которому пытаетесь подключиться. Например: тестовая база данных, тестовый сервер, представляющий какой-то сервис, к которому вы подключаетесь, внешняя файловая система, представленная хранилищем в облаке.
- Держите набор тестов небольшим, цель состоит в том, чтобы проверить ваше соединение, а не охватить все ваши бизнес-кейсы.
- Убедитесь, что вы запускаете его в тестовой среде, так как не хотите повредить свои данные на проде.
Для простоты этой статьи давайте сосредоточимся на тестировании интеграции баз данных.
Пример того, как протестировать создание пользователя:
declare(strict_types=1);
namespace Tests\Integration\DB;
use App\User;
use App\Users\Infrastructure\EloquentUserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class UserRepositoryTest extends TestCase
{
use RefreshDatabase, WithFaker;
/**
* @test
*/
public function shouldCreateUser()
{
$user = factory(User::class)->make();
$repository = new EloquentUserRepository();
$repository->add($user);
$this->assertDatabaseHas(
'users',
['email' => $user->email, 'password' => $user->password]
);
}
}
Инверсия зависимостей в Laravel
Этот принцип – один из моих любимых/
Если вы освоите его, вы напишете код, который может легко внедрить другую реализацию интерфейса, не затрагивая другие части вашего приложения. Так что же такое DI?
“Высокоуровневые модули не должны зависеть от низкоуровневых модулей”
Предположим, что у нас есть 2 класса: CreateUserController и EloquentUserRepository. Поскольку мы вводим репозиторий в контроллер, наш график зависимостей выглядит следующим образом:
Теперь мы можем создать новый UserRepository интерфейс, который будет введен в контроллер и давайте сделаем EloquentRepository для его реализации.
Вы можете видеть, что стрелки зависимостей фактически перевернуты
Вывод
Это только начало того, как писать ремонтопригодные и масштабируемые микросервисы в Laravel. Я надеюсь, что на данный момент Вы видите выгоду от использования принципов SOLID и интеграционные тестирования баз данных. Если у вас есть какие-либо вопросы или комментарии, пожалуйста, оставьте их ниже, и я буду более чем счастлив помочь вам
И как всегда, оставайтесь любопытными и стремитесь к тому, чтобы с каждым днем становиться немного лучше.
Хорошего вам дня!