Предисловие: я пытаюсь использовать шаблон хранилища в архитектуре MVC с реляционными базами данных.
Недавно я начал изучать TDD на PHP, и я понимаю, что моя база данных слишком тесно связана с остальной частью моего приложения. Я читал о репозиториях и использовании контейнера IoC, чтобы "внедрить" его в мои контроллеры. Очень классные вещи. Но теперь есть несколько практических вопросов о дизайне хранилища. Рассмотрим следующий пример.
<?php
class DbUserRepository implements UserRepositoryInterface
{
protected $db;
public function __construct($db)
{
$this->db = $db;
}
public function findAll()
{
}
public function findById($id)
{
}
public function findByName($name)
{
}
public function create($user)
{
}
public function remove($user)
{
}
public function update($user)
{
}
}
Проблема № 1: слишком много полей
Все эти методы поиска используют метод выбора всех полей (SELECT *
). Однако в моих приложениях я всегда пытаюсь ограничить количество полей, которые я получаю, поскольку это часто увеличивает накладные расходы и замедляет работу. Для тех, кто использует этот шаблон, как вы справляетесь с этим?
Проблема № 2: слишком много методов
Хотя этот класс выглядит хорошо сейчас, я знаю, что в реальном приложении мне нужно гораздо больше методов. Например:
- findAllByNameAndStatus
- findAllInCountry
- findAllWithEmailAddressSet
- findAllByAgeAndGender
- findAllByAgeAndGenderOrderByAge
- И т.п.
Как видите, список возможных методов может быть очень и очень длинным. И тогда, если вы добавите в поле выбора вопроса выше, проблема ухудшается. В прошлом я обычно просто помещал всю эту логику прямо в мой контроллер:
<?php
class MyController
{
public function users()
{
$users = User::select('name, email, status')
->byCountry('Canada')->orderBy('name')->rows();
return View::make('users', array('users' => $users));
}
}
С моим подходом к хранилищу я не хочу заканчивать этим:
<?php
class MyController
{
public function users()
{
$users = $this->repo->get_first_name_last_name_email_username_status_by_country_order_by_name('Canada');
return View::make('users', array('users' => $users))
}
}
Проблема № 3: Невозможно сопоставить интерфейс
Я вижу преимущество в использовании интерфейсов для репозиториев, так что я могу поменять свою реализацию (для целей тестирования или для других целей). Я понимаю, что интерфейсы определяют контракт, которому должна следовать реализация. Это замечательно, пока вы не начнете добавлять дополнительные методы в свои репозитории, такие как findAllInCountry()
. Теперь мне нужно обновить свой интерфейс, чтобы также иметь этот метод, в противном случае другие реализации могут его не иметь, и это может сломать мое приложение. От этого кажется безумным... случай, когда хвост виляет собакой.
Шаблон спецификации?
Это наводит меня на мысль, что в репозитории должно быть только фиксированное количество методов (таких как save()
, remove()
, find()
, findAll()
и т.д.). Но тогда как мне запустить конкретные поиски? Я слышал о IsSatisfiedBy()
спецификаций, но мне кажется, что это уменьшает только весь набор записей (через IsSatisfiedBy()
), который явно имеет серьезные проблемы с производительностью, если вы извлекаете данные из базы данных.
Помогите?
Ясно, что мне нужно немного переосмыслить при работе с репозиториями. Может кто-нибудь просветить о том, как это лучше всего обрабатывается?