Модели и сущности. Сходства и различия

Привет!

Сегодня я хотел бы рассказать о разнице между «Моделью» (Model) и «Сущностью» (Entity). Так сложилось, что большинство людей правильно воспринимают понятие Сущность, в то время как понятие Модель часто применяется для объектов, которые на самом деле Сущности. Поскольку общеизвестного и простого объяснения разницы между этими двумя понятиями мне найти не удалось, я вам сегодня расскажу немного о некоторых соображениях на эту тему (моих и не только), которые, я надеюсь, помогут вам разобраться. Давайте посмотрим по отдельности на каждое из этих понятий.

Что такое сущность?

Сущность, как правило, означает некий «предмет», который вы собираетесь держать в неком хранилище (SQL DB, NoSQL DB и пр.). Это может быть новость на вашем сайте, «друг» в списке ваших «друзей» в соц. сети, сообщение в чате и т.д.. Возможно, вы знакомы с этим понятием из ERM («Entity-relationship model» — модель сущность-связь).

В связи с тем, что работать с «сырыми» данными, пришедшими напрямую из БД, во всех уровнях приложения очень неудобно, хорошей практикой является создание слоя доступа к данным (Data Access Layer) , где результаты из БД «мапятся» (проецируются, отображаются, приводятся в соответствие) на экземпляры классов, определённых в этом слое.

Этим самым вы создаете структуру сущностей из БД, с которой вы работаете, в объектно-ориентированном пространстве. Таким образом работают ORM (Object Relation Mapper — объектно-реляционные отображения). Эта часть приложения (ORM) получает данные из БД и создает экземпляры классов, определённых вами сущностей, каждый из которых представляет одну запись результата выборки. Отсюда вывод: назначение сущности — представление данных. И всё!

Чем же это удобно? А тем, что вы можете отделить БД и слой доступа к базе данных от остальной части вашего приложения, в которой сконцентрирована вся бизнес-логика. Если вам вдруг понадобится проверить, как ваша система работает с каким-то определённым набором данных, вы можете просто создать экземпляр класса с необходимыми свойствами и передать его функции, с которую мы тестируем. Вот пример:

// класс сущности письма
class LetterEntity {
    public $id;
    public $from;
    public $to;
    public $title;
    public $text;
 
    public function __construct( $id, $from, $to, $title, $text ) {
        $this->id = $id;
        $this->from = $from;
        $this->to = $to;
        $this->title = $title;
        $this->text = $text;
    }
}

// Функция, которую проверяем
function create_letter_html(LetterEntity $letter) {
    return '<h1>#' . $letter->id . ' ' . $letter->title . '</h2>'
    	. '<p>From: ' . $letter->from 
    	. ', To: ' . $letter->to 
    	. ', Text: ' . $letter->text . '</p>';
}
 
// Тест
class CreateHtmlTest {
     public function testOne() {
        $letter = new LetterEntity(1, 'me@gmail.com', 'you@gmail.com', 'Title', 'My Text');
        $this->assertEquals( 
            create_letter_html($letter), 
            '<h1>#1 Title</h2><p>From: me@gmail.com, To: you@gmail.com, Text: My Text</p>';
        );
    }
}

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

$letter = new LetterEntity(DB::query('SELECT * FROM `letters` WHERE `id` = 1'));
$letter = new LetterEntity(Memcache::get('letter1'));
$letter = new LetterEntity(HttpLetterService::fetch(1));

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

Что такое модель?

Для того, чтобы понять, что же такое Модель, вам необходимо понимать MVC (Model-View-Controller, Модель-Вид-Контроллер) паттерн (шаблон проектирования). Согласно этой концепции, у вас есть Контроллер, который вызывается, к примеру, через HTTP-запрос и решает, что делать в целом. «В целом» означает, что он определяет, что пользователь хочет увидеть главную страницу, страницу входящих сообщений, архив новостей и т.п.. Он получает соответствующую текущему запросу модель и передает эту Модель в Вид, который в большинстве случаев представляет из себя обычный шаблон Smarty, Twig или чего-то в этом духе.

Простенький контроллер мог бы выглядеть так:

class MailController {
     public function showLetter($id) {
        $letter = ORM::getLetter($id);
        return TemplateEngine::render('showletter.tpl', array( 'letter' => letter));
    }
}

И тут вы можете задать вполне естественный вопрос: И где же тут Модель? Сущность из первой части этого поста — это та самая модель в этом примере! Она представляет запрошенную информацию. И это тот самый нюанс, из-за которого многие путают понятие «Модель» и «Сущность», потому что в некоторых случаях оба эти термина — одно и то же или почти одно и то же.

Но! При всём при этом есть множество случаев, в которых нам нужна «настоящая» модель, потому что модель в MVC среде должна содержать большую часть вычислений, логики и обработки (бизнес-логика). Да, это важный момент! Бизнес-логика должна быть внутри модели.

Можно попробовать создать гибридную Сущность-Модель и добавить бизнес-логику в класс сущности, определив несколько новых методов, которые делают нужные вам действия. Но давайте представим, что вам нужно, чтобы какие-то данные из БД и сторонней библиотеки делали что-то. Если вы реализуете бизнес-логику, используя другую библиотеку в вашем классе сущности, вам нужно добавить новую зависимость к ней, и тестирование станет гораздо запутаннее. Поэтому давайте всё-таки разделим Модель и Сущность:

class LetterModel {
    public function __construct(LetterEntity $entity, TextFormatter $formatter) {
        $this->entity = $entity;
        $this->formatter = $formatter;
    }
 
    public function getId() { return $this->entity->id; }
    public function getTitle() { return $this->entity->title; }
    public function getText() { return $this->entity->text;  }   
 
    public function getFormattedText() {
        $text = $this->formatter->format($this->entity->text);
        return htmlentities($text);
    }
}
 
class TestLetterModel {
    public function testOne() {
        $formatter = new TextFormatter();
        $text = 'My *Text*';
        $formattedText = $formatter->format($text);
 
        $entity = new LetterEntity(1, 'me@gmail.com', 'you@gmail.com', 'Title', $text);
 
        $model = new LetterModel($entity, $formatter);
        $this->assertEquals( 
            $model->getFormattedText(), 
            htmlentities( $formattedText )
        );
     }
}

Что это вам даёт? Теперь ваша ORM библиотека не должна ничего знать о TextFormatter! То есть такого, как в примере ниже, у вас не будет:

$letter = ORM::getLetter(1);
$letter->getFormattedText();

Здесь подразумевается, что ORM::getLetter возвращает сущность, заключающую в себе бизнес-логику. Очевидно, что ORM-классу приходится знать о существовании TextFormatter’a. Как ещё можно было бы осведомить письмо об этом?

В среде программистов считается хорошей практикой разделение такой большой части вашего приложения, как ORM, от остальной части проекта. В качестве ORM в PHP зачастую используют Doctrine 2. Поэтому будет гораздо лучше разделить Сущность и Модель, как показано в примере выше, и передавать TextFormatter по своему усмотрению.

$letter = ORM::getLetter(1);
$model = new LetterModel( $letter, new MyModifiedTextFormatter());
$model->getFormattedText()

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

class ShoppingCartModel {
    protected $products = [];
    protected $vouchers = [];
    ...
    public function getCost() {
        $cost = 0;
        foreach ($this->items as $item){ $cost += $item->getPrice(); }
        foreach ($this->vouchers as $voucher){ $cost -= $voucher->getCredit(); }
        return $cost;
    }
}

Выводы

В заключение, хотелось бы кратко изложить выше объяснённые различия и схожести между сущностями и моделями.

Похожи тем, что:

  • Используются для доступа к данным данных;
  • Используются для увеличения тестируемости кода и разделения приложение на компоненты.

Различаются тем, что:

  • Сущности относятся к ORM, Модели относятся к MVC;
  • Сущности представляют информацию, Модели содержат бизнес-логику.

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

Спасибо за то, что дочитали до конца! :) Я очень надеюсь, что эта статья о разнице между сущностью и моделью вам помогла разобраться, и ваш уровень в программировании стал ещё выше!

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