Pull to refresh

Active Record против Data Mapper-а для сохранения данных

Reading time4 min
Views80K
Эти 2 шаблона проектирования описаны в книге Мартина Фаулера «Шаблоны корпоративных приложений» и представляют собой способы работы с сохранением данных в объектно-ориентированном программировании.

Пример шаблона Active Record


class Foo
{
    protected $db;
    public $id;
    public $bar;
     
    public function __construct(PDO $db)
    {
        $this->db = $db;
    }
 
    public function do_something()
    {
        $this->bar .= uniqid();
    }
 
    public function save()
    {
        if ($this->id) {
            $sql = "UPDATE foo SET bar = :bar WHERE id = :id";
            $statement = $this->db->prepare($sql);
            $statement->bindParam("bar", $this->bar);
            $statement->bindParam("id", $this->id);
            $statement->execute();
        }
        else {
            $sql = "INSERT INTO foo (bar) VALUES (:bar)";
            $statement = $this->db->prepare($sql);
            $statement->bindParam("bar", $this->bar);
            $statement->execute();
            $this->id = $this->db->lastInsertId();
        }
    }
}
 
//Insert
$foo = new Foo($db);
$foo->bar = 'baz';
$foo->save();

В этом упрощенном примере, дескриптор базы данных вводится в конструкторе Foo (Использование инъекции зависимостей здесь позволяет тестировать объект без использования реальной базы данных), и Foo использует его, чтобы сохранять свои данные. Do_something — просто метод-заглушка, заменяющий бизнес логику.

Преимущества Active Record


  • Писать код с Active Record получается быстро и легко, в том случае, когда свойства объекта прямо соотносятся с колонками в базе данных.
  • Сохранение происходит в одном месте, что позволяет легко изучить, как это работает.

Недостатки Active Record


  • Модели Active Record нарушаю принципы SOLID. В частности, принцип единой ответственности (SRP — «S» в принципах SOLID). Согласно принципу, доменный объект должен иметь только одну зону ответственности, то есть только свою бизнес-логику. Вызывая его для сохранения данных, вы добавляете ему дополнительную зону ответственности, увеличивая сложность объекта, что усложняет его поддержку и тестирование.
  • Реализации сохранения данных тесно связана с бизнес-логикой, а это означает, что если вы позже захотите использовать другую абстракцию для сохранения данных (например для хранения данных в XML-файле, а не в базе данных), то вам придется делать рефакторинг кода.

Пример Data Mapper-а


class Foo
{
    public $id;
    public $bar;
 
    public function do_something()
    {
        $this->bar .= uniqid();
    }
}
 
class FooMapper
{
    protected $db;
 
    public function __construct(PDO $db)
    {
        $this->db = $db;
    }
    public function saveFoo(Foo &$foo)
    {
        if ($foo->id) {
            $sql = "UPDATE foo SET bar = :bar WHERE id = :id";
            $statement = $this->db->prepare($sql);
            $statement->bindParam("bar", $foo->bar);
            $statement->bindParam("id", $foo->id);
            $statement->execute();
        }
        else {
            $sql = "INSERT INTO foo (bar) VALUES (:bar)";
            $statement = $this->db->prepare($sql);
            $statement->bindParam("bar", $foo->bar);
            $statement->execute();
            $foo->id = $this->db->lastInsertId();
        }
    }
}
 
//Insert
$foo = new Foo();
$foo->bar = 'baz';
$mapper = new FooMapper($db);
$mapper->saveFoo($foo);

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

Преимущества Data Mapper-а


  • Каждый объект имеет свою зону ответственности, тем самым следую принципам SOLID и сохраняя каждый объект простым и по существу.
  • Бизнес-логика и сохранение данных связаны слабо, и если вы хотите сохранять данные в XML-файл или какой-нибудь другой формат, вы можете просто написать новый Mapper, не притрагиваясь к доменному объекту.

Недостатки Data Mapper-а


  • Вам придется гораздо больше думать, перед тем как написать код.
  • В итоге у вас больше объектов в управлении, что немного усложняет код и его отладку.

Сервис-объекты


При использовании шаблона проектирования Data Mapper, вызывающий код должен выбрать Mapper и бизнес-объект и связать их вместе. Если это код вызова в контроллере, то в конечном счете ваша модель «утекает» в контроллер, что может вызвать большие проблемы при поддержке и юнит-тестировании. Эта проблема может быть решена путем введения объекта-сервиса. Сервис является воротами между контроллером и моделью и связывает доменный объект с Mapper-ом по мере необходимости.

Следует помнить, что M в MVC, представляет собой слой абстракции модели, а не объект модели. Так может быть несколько типов объектов в одной модели (в приведенном выше примере, у вас может быть объект сервиса, доменный объект и объект Mapper-а, выступающие в роли единой модели). С другой стороны, если вы используете модели Active Record, ваша модель может быть представлена лишь одним объектом.

Варианты использования


Объекты Active Record исторически были очень популярны из-за того, что они проще, легче в понимании и быстрее в написании, поэтому многие фреймворки и ORM используют Active Record по умолчанию.

Если вы уверены, что вам никогда не понадобиться менять слой сохранения данных (если вы имеете дело с объектом, который представляет из себя INI-файл, например), или вы имеете дело с очень простыми объектами, в которых не так много бизнес-логики, или просто предпочитаете держать все в небольшом количестве классов, тогда шаблон Active Record это то, что вам нужно.

Использование Data Mapper-а хотя и ведет к более чистому, простому в тестировании и поддержке коду, и обеспечивает большую гибкость, — цена этому, — повышение сложности. Если вы еще не пробовали его использовать, то дайте ему шанс, — вам должно понравиться.

Это перевод статьи Рассела Волкера.
Tags:
Hubs:
+31
Comments62

Articles