Оргенизация слоя хранения данных это всегда не простая задача, с кучей кейсов, вроде блокировки перезаписи, транзакции, сохранение целостности данных. В какой-то момент появились СУБД и обещали решить эти проблемы, но с ними появились новые - как нам работать с состоянием, которое мы храним?
Существует 4 основных подхода для организации рабоыт с базой данных, Table Gateway, Row Data Gateway, Active Record и Data Mapper. Все эти подходы объеденяет то, что они скрывают от нас базу данных и нюансы работы с ними (в частности SQL). На сегодняшний день самым популярным подходом являются Active Record и Data Mapper, все о них слышали, но для того что бы более полно представлять, как развивалась идея, стоит рассказать и о первых двух.
Table Data Gateway предоставляет нам объектное представление отдельных таблиц, с доступом ко всем рядам оных. Его основное предназначение - спрятать SQL как деталь реализации, предоставиви простой CRUD интерфейс для работы с таблицами в соответствии с нуждами приложения. Рассмотрим на примере (для удобства я заменил обычный SQL на Database компонент из Laravel с целью использовать тамошний query builder)
class UserTableGateway {
private $table;
public function __construct() {
$this->table = DB::table('users');
}
public function insert($name, $email, $password) {
$this->table->insert(compact('name', 'email', 'password'));
}
// пароль должен обновляться отдельно от профиля
public function updateProfile($id, $name, $email) {
$this->table->where('id', $id)->update(compact('name', 'email'));
}
public function findCoolestUser() {
return $this->table->orderBy('rating', 'DESC')->first(); //
}
}
$usersTable = new UserTableGateway();
$usersTable->insert('Sergey', 'fesor@example.com`, password_hash('somepassword', PASSWORD_DEFAULT));
$user = $usersTable->findCoolestUser(); // вернет нам один ряд Основная идея тут - держать все что относится к формированию запросов в одном месте, что бы наш query builder, или же PDO, не расползался по системе. Не изолировать код от хранилища, а предоставить удобный способ взаимодействия с ним. Возможность инкапсулировать различные запросы в рамках этого подхода позволяет сильно уменьшить вероятность ошибок. Однако работать с "сырыми" данными не очень удобно, обычно мы все же хотим работать со всеми данными как с объектами.
На помощь к нам придет Row Data Gateway. При этом подходе мы проэцируем отдельные ряды нашей таблицы на объекты. Ряды знают о том, к какой таблице они пренадлежат, и могут сами себя обновлять и добавлять в таблицу.
class UserGateway {
// сделав свойства публичным я хочу подчеркнуть то,
// что этот класс это просто структура данных, состояние без поведения
private $id;
public $name;
public $email;
public $password;
// еще раз, это проекция таблицы, а не объект бизнес логики
public function __constructor(string $id = null) {
if ($id) {
$this->id = $id;
$this->fetchRow();
} else {
// если ID не задан, то мы генерируем ноывй
$this->id = Cuid::cuid();
}
}
private function fetchRow() {
$row = DB::where('id', $this->id')->first();
if (!$row) {
throw new \UnableToCreateGatewayException('');
}
}
}
Данный объект представляет собой простую структуру данных без какого либо поведения. По сути мы могли бы..
Некоторым стало неудобно юзать структурки и они захотели добавлять туда поведение.
AR и Row Data Gateway не очень удобно тестировать. Нам хочется полной независимости от базы данных.
По сути мы работаем с объектами нашей базы данных как с обычными объектами. Разлица лишь в том, что каждый способ предоставляет больше возможностей для изоляции от хранилища. Table Data Gateway заставляет нас работать со всей таблицей целиком, Row Data Gateway - с отдельными рядами таблицы. Active Record является развитием идеи Row Data Gateway но уже позволяет инкапсулировать дополнительное поведение. Data Mapper же вообще не приязан к структуре базы данных, и, так же как и в случае с AR, мы можем примешивать объектам поведение.
Поскольку первые два подхода практически не используются, рассмотрим Active Record и Data Mapper. В чем координальное отличие?
В рамках Active Record (или Active State) мы работаем с объектами как отображением элементов нашей базы данных, как если бы у нас был прямой доступ к ним без SQL прослойки. В DataMapper мы работаем исключительно с нашими объектами, которые лежат в памяти, и просим отдельную штуку (мэппер) что бы тот синхронизировал состояние объектов в памяти и в базе (сохранил состояние по сути).
Доустим у нас есть некий граф объектов, с которым мы работаем в рамках бизнес транзакции. И мы должны сохранить изменения в базу, то есть объект на верху графа и все связанные с ним сущности. Если руководствоваться паттерном "Информационный эксперт", заниматься этим должен тот, кто знает как это делать. В случае AR эта логика выносится прямо в сущности и мы явно задаем в каком порядке что сохранять. В случае же с DM у сущности нет таких знаний и мы выделяем все в отдельный компонент, что дает нам дополнительные варианты как это организовать удобнее для нас. От тупого разруливания графа "руками", до алгоритмов, которые разруливают это автоматически.
Как доставать объекты из хранилища - это отдельная темам для разговора, и тот и другой подход никак не это не влияют. Спор AR vs DM - это спор о том кто сохраняет объекты.
Это основное различие в подходах. Мэпить любую структуру на любую структуру мы можем и при том и при другом подходе, но только при варианте с Active Record обычно так не делают (что бы сохранить иллюзию прямой работы с базой), так как этот шаблон подразумевает максимально упрощенную реализацию. Именно по этому при использовании AR обычно говорят о тестной связанности с базой, а при DM - слабой (мы вообще от нее не зависим по сути).
Остальные нюансы, вроде UnitofWork, Ideniny Map, работа со связями, ленивая инициализация связанных объектов, все это - приблизительно одинаково реализуется при обоих подходах и никакого отношения непосредственно к ActiveRecord или DataMapper не имеет. Но без этого всего сами подходы не предоставляют нам той гибкости, которую мы ожидаем получить от ORM.
На негодняшний день в чистом виде в рамках существующих ORM вы можете встретить только DataMapper. ORM на основе Active Record так или иначе внутри используют намного более сложные концепции, дабы упростить разработчикам жизнь и снизить сложность.
- Doctrine2 - Самое мощное решение из существующих на данный момент.
- Spot2 - легковесная ORM а базе Doctrine DBAL без магии.
- Analogue - легковесая ORM, использующая в качестве основы Laravel Database
- Atlas.Orm - сырая, но интересная ORM, которая пытается полностью вынести Persistance Model из слоя бизнес логики, но без какой либо магии.
TODO
TODO
TODO