Наверняка все слышали что про модели, MVC, AR и другие замечательные слова.
Но все ли до конца понимают, что эти слова означают?
Все ли понимают что такое модель и как она должна выглядеть
Давайте порассуждаем (и не только) на эту тему.
Паттерны
Первым делом обратимся к паттернам и их определениям модели (источник design-pattern.ru):
- MVC (Model View Controller) — модель данных. Кратенько и очень абстрактно;
- AR (Active Record) — один объект управляет и данными, и поведением. Казалось бы рабочая модель и много где используется, но хорошо работает она ровно до тех пор, пока бизнес-логика не становится слишком насыщенной, и тогда идея хранить все в одной корзине не кажется такой уж рабочей;
- DM (Domain Model) — каждый объект представляет собой отдельную значащую сущность. Вот это самое правильное определение для модели. Модель — это исключительно бизнес-логика и ничего больше!
*Под формулировкой "Модель — это исключительно бизнес-логика" подразумевается что модель — отражает сущности, данные и поведение предметной области, и никак не касается данных и объектов сервисного слоя и слоя приложения.
**Модель и сущность далее по тексту — это одно и тоже, и подразумевают domain object (в рамках концепции Domain Model).
После того как решили что модель — это исключительно бизнес-логика, перейдем к вопросу формирования этих моделей.
Но сначала пару слов про хранение.
Каждой модели свой репозиторий
Т.к. модель это в слое домена, то нам нужна прослойка, которая будет сохранять в БД наши модели.
Самый простой и правильный вариант — Репозиторий.
Еще немного теории и определений:
Репозиторий посредничает между уровнями области определения и распределения данных (domain and data mapping layers), используя интерфейс, схожий с коллекциями для доступа к объектам области определения.
Каждый репозиторий работает только с 1 моделью.
Не должно быть репозиториев типа BlogRepository
который работает со всеми сущностями блога, или PostRepository
который сохраняет еще и комментарии.
Графически структуру можно представить таким образом:
Важно отметить что в слое домена используются только интерфейсы, без конкретных реализаций.
Это нужно для изоляции домена, чтобы исключить любые зависимости на конкретных реализациях и как следствие удобства тестирования (реализовали интерфейс для тестов домена и ок).
Так как в реальных условиях, у нас скорее всего будет только 1 реализация репозитория, можно использовать репозиторий без интерфейсов домена, а сразу в сервисном слое.
Главное не строить зависимости на конкретной структуре данных, а при тестировании можно использовать моки.
Модели — это сущности, а не таблицы
В большинстве случаев проектирование системы начинается именно со структуры данных (БД).
И дальше уже проектирование моделей также отталкивается от их хранения, т.е. от таблиц.
Но это в корне неправильный подход.
Рассмотрим подробнее на примере блога.
Какие модели мы имеем:
- Пост
- Автор
- Комментарий
- Тэги
- Категории
Чтобы не было нагромождения кода, рассматривать будем только модель поста и наращивать функционал будет постепенно.
Начнем с поста:
interface PostRepository
{
public function save(Post $model);
}
class Post
{
protected $id;
protected $title;
protected $content;
public function setId(int $id)
{
$this->id = $id;
}
public function getId(): ?int
{
return $this->id;
}
public function setTitle(string $title)
{
$this->title = $title;
}
public function getTitle(): string
{
return $this->title ?: '';
}
public function setContent(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content ?: '';
}
}
Работа с такой моделью и репой, будет выглядеть примерно так:
$post = new Post();
$post->setTitle('Title');
$post->setContent('...');
$repo->save($post);
Теперь внедрим сущность Автор
, для этого нам нужно добавить методы в исходную модель:
class Post
{
// ...
protected $author;
public function setAuthor(Author $author)
{
$this->author = $author;
}
public function getAuthor(): Author
{
return $this->author;
}
}
Пример работы:
$author = $authorRepo->getById(1);
$post->setAuthor($author);
$repo->save($post);
Теперь внедрим сущность Комментарий
, в данной ситуации тоже бы добавить сеттер и соответствующее поле, но если говорить про бизнес-смыслы, то у нас нет такого понятия как "указать комментарии", у нас есть такие понятия как "добавить комментарий" и "удалить комментарий".
Поэтому нашу исходную модель преобразуем таким образом:
class Post
{
// another code
protected $comments;
protected $addComments = [];
protected $removeComments = [];
public function getComments()
{
return $this->comments;
}
public function addComment(Comment $comment)
{
$this->addComments[] = $comment;
}
public function getAddComments()
{
return $this->addComments;
}
public function removeComment(Comment $comment)
{
$this->removeComments[] = $comment;
}
public function getRemoveComments()
{
return $this->removeComments;
}
}
Работа с комментариями в таком случае у нас будет выглядеть так:
$newComment = new Comment();
$newComment->setContent("...");
$removeComment = $commentRepo->getById(1);
$post->addComment($newComment);
$post->removeComment($removeComment);
$repo->save($post);
В момент сохранения поста, у нас также должны обрабатываться удаление и добавление комментариев.
Но сам PostRepository
не должен работать с хранилищем комментариев, он должен делегировать это на CommentRepository
.
То есть репозиторий постов, должен выглядеть примерно так:
class ConcretePostRepository implements PostRepository
{
protected $commentRepository;
public function setCommentRepository(CommentRepository $commentRepository)
{
$this->commentRepository = $commentRepository;
}
public function save(Post $post)
{
if ($post->getId()) {
$this->update($post);
}
else {
$this->insert($post);
}
foreach ($post->getAddComments() as $comment) {
$this->commentRepository->save($comment);
$this->linkCommentToPost($post, $comment);
}
foreach ($post->getRemoveComments() as $comment) {
$this->unlinkCommentToPost($post, $comment);
$this->commentRepository->remove($comment);
}
}
}
При этом важно учесть, что если мы посмотрим на связь "пост — комментарий" со стороны комментария, то у нас не должно быть метода setPost
, т.к. он нарушает связь "целое — часть".
Причем метод getPost
эту логику не нарушает и вполне может существовать.
В примере ниже, мы меняем "владельца" комментария, что противоречит бизнес-логике: мы не можем комментарий переместить в другой пост, мы можем только добавить, удалить или изменить комментарий.
$post = $repo->getById(1);
$comment = new Comment();
$comment->setContent('...');
$comment->setPost($post); // этого метода быть не должно!
$commentRepo->save($comment);
$post = $comment->getPost(); // данный метод корректен
Перейдем к сущности Тэги
, если говорить про бизнес-смысл, то здесь вполне корректным является действие "установить тэги".
Но если говорить про удобство использования, методы addTag
и removeTag
также стоит добавить.
Таким образом дополняем модель следующими методами:
class Post
{
// another code
protected $tags;
public function setTags(TagCollection $tags)
{
$this->tags = $tags;
}
public function addTag(Tag $tag)
{
if (!$this->tags) {
$this->tags = new TagCollection;
}
$this->tags->add($tag);
}
public function removeTag(Tag $tag)
{
if ($this->tags instanceof TagCollection) {
$this->tags->remove($tag);
}
}
}
Работа с тегами будет выглядеть так:
$tag1 = $tagRepo->getById(1);
$tag2 = new Tag();
$tag2->setValue('...');
$tag3 = $tagRepo->getById(3);
$post->setTags(new TagCollection($tag1, $tag2));
// или
$post->addTag($tag1);
$post->addTag($tag2);
$post->removeTag($tag3);
$repo->save($post);
Если посмотреть на связь "пост — тэги" со стороны сущности Тэги
, то мы опять не может добавить метод setPosts
т.к. нарушаем связь "целое — часть" (потому что post has tag
, а не tag has post
).
Но при этом и метод getPosts
мы также не можем использовать, потому что он также нарушает связь "целое — часть".
Правильным решение будет вынести метод получения списка постов конкретного тега в репозиторий:
interface PostRepository
{
public function getListByTag(Tag $tag);
}
Ну и наконец перейдем к сущности Категории
.
Казалось бы тут все тоже самое что и у тэгов, но как раз наоборот: с точки зрения бизнес-логики посты являются частью категорий, т.е. посты "складываются" в категории как в папки, а не категории привязываются к постам, как в случае с тэгами.
Поэтому в модель поста вы добавляем лишь геттер:
class Post
{
// another code
protected $categories;
public function getCategories()
{
return $this->categories;
}
}
А работа с категориями будет выглядеть так:
$post = $postRepo->getById(1);
$cat1 = $categoryRepo->getById(1);
$cat1->addPost($post);
$categoryRepo->save($cat1); // сохраняем привязку к категории
$cat2 = $categoryRepo->getById(2);
$cat2->addPost($post);
$categoryRepo->save($cat2); // сохраняем привязку к категории
$cat3 = $categoryRepo->getById(3);
$cat3->removePost($post);
$categoryRepo->save($cat3); // убираем привязку к категории
[$cat1, $cat2] = $post->getCategories();
Бизнес-действия
Если посмотреть на конечную реализацию модели Post
, то можно заметить заметить, что она получилась достаточно громоздкой.
А если еще внимательнее посмотреть, что она совсем не содержит никаких действий, а только хранит данные.
class Post
{
protected $id;
protected $title;
protected $content;
protected $author;
protected $tags;
protected $comments;
protected $addComments = [];
protected $removeComments = [];
public function setId(int $id)
{
$this->id = $id;
}
public function getId(): ?int
{
return $this->id;
}
public function setTitle(string $title)
{
$this->title = $title;
}
public function getTitle(): string
{
return $this->title ?: '';
}
public function setContent(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content ?: '';
}
public function setAuthor(Author $author)
{
$this->author = $author;
}
public function getAuthor(): Author
{
return $this->author;
}
public function getComments()
{
return $this->comments;
}
public function addComment(Comment $comment)
{
$this->addComments[] = $comment;
}
public function getAddComments()
{
return $this->addComments;
}
public function removeComment(Comment $comment)
{
$this->removeComments[] = $comment;
}
public function getRemoveComments()
{
return $this->removeComments;
}
public function setTags(TagCollection $tags)
{
$this->tags = $tags;
}
public function addTag(Tag $tag)
{
if (!$this->tags) {
$this->tags = new TagCollection;
}
$this->tags->add($tag);
}
public function removeTag(Tag $tag)
{
if ($this->tags instanceof TagCollection) {
$this->tags->remove($tag);
}
}
}
Например, при добавлении комментария к посту, у нас автоматически должно уходить уведомление автору этого поста.
Причем уходить уведомление должно, только после сохранения комментария.
Чтобы решить и ту и другую задачу, можно ввести "бизнес-действия", которые будут обрабатываться в момент сохранения.
Это атомарные классы, которые выполняют одно конкретное действие.
Интерфейсы могут выглядеть таким образом:
abstract class BusinessOperation
{
abstract public function run();
public static function createInstance(): self
{
// DI контейнер
return Container::getInstance()->make(get_called_class());
}
}
abstract class Model
{
private $operations = [];
protected function addOperation(BusinessOperation $item)
{
$this->operations[] = $item;
}
public function getOperations(): array
{
return $this->operations;
}
}
Реализация действия "уведомить автора поста" может выглядеть так:
class NotifyAuthorAboutComment extends BusinessOperation
{
protected $service;
protected $post;
public function __construct(NotifyService $service)
{
$this->service = $service;
}
public function setPost(Post $post)
{
$this->post = $post;
}
public function run()
{
$author = $this->post->getAuthor();
$notify = new Nofity();
$notify->setOwner($author);
$notify->setContent("Новые комментарий к записи '{$this->post->title}'");
$this->service->send($notify);
}
}
Соответственно репозиторий у нас тоже становиться более абстрактным и будет выглядеть так:
abstract class ModelRepository
{
protected function saveInternal(Model $model)
{
try {
$this->startTransaction();
if ($model->isNewRecord()) {
$this->insert($model);
}
else {
$this->update($model);
}
foreach ($model->getOperations() as $operation) {
$operation->run();
}
$this->commitTransaction();
}
catch (Throwable $e) {
$this->rollbackTransaction();
throw $e;
}
}
}
class ConcretePostRepository extends ModelRepository implements PostRepository
{
public function save(Post $post)
{
$this->saveInternal($post);
}
}
А реализация самой модели становится более краткой, более понятной и удобной к расширению:
class Post extends Model
{
protected $id;
protected $title;
protected $content;
protected $author;
protected $tags;
protected $comments;
public function setId(int $id)
{
$this->id = $id;
}
public function getId(): ?int
{
return $this->id;
}
public function setTitle(string $title)
{
$this->title = $title;
}
public function getTitle(): string
{
return $this->title ?: '';
}
public function setContent(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content ?: '';
}
public function setAuthor(Author $author)
{
$this->author = $author;
}
public function getAuthor(): Author
{
return $this->author;
}
public function getComments()
{
return $this->comments;
}
public function addComment(Comment $comment)
{
$action = AddComment::createInstance();
$action->setPost($this);
$action->setComment($value);
$this->addOperation($action);
$action = NotifyAuthorAboutComment::createInstance();
$action->setPost($this);
$this->addOperation($action);
}
public function removeComment(Comment $comment)
{
$action = RemoveComment::createInstance();
$action->setPost($this);
$action->setComment($value);
$this->addOperation($action);
}
public function setTags(TagCollection $tags)
{
$this->tags = $tags;
}
public function addTag(Tag $tag)
{
if (!$this->tags) {
$this->tags = new TagCollection;
}
$this->tags->add($tag);
}
public function removeTag(Tag $tag)
{
if ($this->tags instanceof TagCollection) {
$this->tags->remove($tag);
}
}
}
Почему плохо нарушать связь "целое — часть"
Ранее неоднократно упоминалось что мы не можем реализовать тот или иной метод из-за нарушения связи "целое — часть".
Рассмотрим на примере процедуры оформления заказа, чтобы понять насколько критично может быть нарушение данной связи.
Входные данные: заказ у нас имеет товары, информацию об оплате и доставке, при изменении статуса оплаты у нас меняется статус самого заказа, статус доставки (разрешается доставка) и отправляются уведомления ответственным сотрудникам и самому клиенту.
Допустим мы получили от клиента оплату и сохраняем ее таким образом:
// в данной ситуации не важно как мы получили объект оплаты, важно то как мы его сохранили
$payment = $repo->getById(1);
$payment = $order->getPayment();
$payment->setPaidAmount(100);
if ($payment->isPaid()) {
$payment->setStatus(IS_PAID);
}
$repo->save($payment);
Что произойдет?
Мы изменим сумму и статус оплаты, и полностью проигнорируем остальную цепочку действий.
Но если обработку полученной оплаты мы будем выполнять таким образом:
$order = $repo->getById(1);
$order->setPaidAmount(100);
$repo->save($order);
То при сохранении заказа выполнятся все связные действия и обновятся все необходимые данные.
Очень важно контролировать связь "целое — часть", чтобы бизнес-логика отрабатывала как нужно.
На самом деле все кроется на уровне формулировок: вместо "получили оплату", нужно использовать формулировку "получили оплату по заказу", и тогда станет ясно где целое и как нужно обработать данные.
Пара слов про репозитории
Часто можно заметить в интерфейсах/реализациях подобные конструкции:
interface BaseRepository
{
/**
* @param Condition $condition условие выборки
*/
public function getList(Condition $condition);
}
И это не правильно.
При таком решении встает сразу парочка неудобных вопросов:
- что указывать в условие: поля домена или поля таблицы? При первом варианте придется дополнительно преобразовывать условия домена в условия БД. При втором варианте вы привязываетесь к реализации конкретного хранилища (см. пункт 2);
- как изменять структуру хранения без боли? Например, мы изменили структуру данных: ранее у нас была связь 1:M, теперь стала N:M. Т.е. фактически мы убрали столбец и создали новую таблицу. Следовательно нам теперь нужно изменить все использования старого столбца в Condition, и переделать их на вызовы связной таблицы (а если условия составные, то это не так уж и просто будет).
Как этого избежать?
Ответ на самом деле прост — не использовать абстрактные условия, а выносить все в конкретные методы:
interface CommentRepository
{
public function getListByPost(Post $post);
public function getLastByPost(Post $post);
public function getListPopular();
public function getById(int $id);
}
При такой реализации мы полностью абстрагируемся от структуры хранения и у нас уже не стоит вопроса что указывать в условии, т.к. мы зависим только от домена. И при изменении структуры данных нам нужно будет только переопределить реализацию методов в репозитории.
Заключение
Модели — это очень полезная вещь, но важно понимать что скрывается за этим термином.
Когда мы говорим про проекты со сложной логикой, без хорошо спроектированного слоя домена, будет очень проблемно вносить изменения и в дальнейшем поддерживать такой проект.
Представленный вариант работы с моделями дает ряд полезностей:
- позволяет полностью абстрагировать слой домена от реализации — это дает возможность эффективно тестировать модели и связи между ними;
- упрощает разработку и проектирование самих моделей т.к. не приходится думать о том как будут данные хранится, а сконцентрироваться на том как система должна функционировать;
- упрощает масштабирование и доработки реализации — за счет реализации конкретных методов у репозитория и гибкий бизнес-операций;
На этом все.
UPD: спасибо lair за теоретический ликбез в комментах. В целом все остались при своем мнении, но чтиво полезное!
lair
Вы когда говорите об определениях — вы бы на источник-то определения ссылались?
"Правильное" по каким критериям? Вам так нравится?
rpsv Автор
Вы когда комментарии пишите, сначала статью до конца дочитайте ;)
А источники да, добавил.
lair
А зачем, если первоначальная посылка ошибочна:
Из ложной посылки можно сделать любые выводы, но какая в них ценность?
Это не источник, а на заборе написано. Вот, например, вы их цитируете:
Открываем PoEAA, на который они, типа, ссылаются:
Мораль: не читайте то, что написано на заборе, читайте первоисточники.
lair
Кстати, еще об определениях.
Создается ощущение, что вы решили, что (в Domain Model) модель и сущность — это одно и то же. Так вот, нет:
(снова PoEAA)
Объектная модель домена. То есть, внезапно, слово "модель" относится к целому, а не к его частям. Вот подтверждающая цитата, где модель и составляющие ее объекты противопоставляются:
… это, конечно, если мы говорим в терминах Фаулера. Если мы перейдем к терминам Эванса, картинка может и поменяться.
rpsv Автор
Это конечно замечательно, что вы столько выдержек из теории выдали (спасибо, правда), НО из этих выдержек абсолютно не следует, что представление модели изложенное в статьи противоречит этим определениям.
Да, каюсь, некорректно привел информацию о модели в парадигме MVC, но речь в статье НЕ заключалась в объяснении MVC, а рассмотрение конкретного примера работы с моделями.
А в чем тут ложность? Вы сами приводите сноску о том, что модель — это объект предоставляющий информацию о домене. По вашему домен != бизнес-логика?
А что в вашей сноске подтверждает ваше утверждение? Модель и сущность — это термины означающие одно и то же: объект предметной области обладающей поведением и данными данной предметной области. Если вы имеете ввиду различные вспомогательные ValueObject и определяете их как сущности — это опять вопрос терминологии. Даже если мы возьмем такой пример VO как «деньги», то это тоже модель, но без поведения как такового.
Вообще не подтверждающая цитата. Здесь лишь говорить о том что модели могут раздуваться до огромных размеров, но это уже вопросы проектирования и реализации, а не подхода как такового. В статье идет разделение на «целое и часть» в рамках конкретной сущности бизнес-логики (например «пост — комментарий»).
То есть допустим, если у нас есть объект «Департамент», который действительно может быть большим, НО большой объект != сложный объект. Если декомпозировать эту модель на его «части» и логику реализовать с помощью бизнес-операций (пример реализации в статье), то объект будет просто большим, но ничуть не сложным.
lair
Эти, выдержки, однако, показывают, что ваше утверждение, что "модель — это исключительно бизнес-логика", ими не подтверждается.
Ну так не надо выдавать конкретный пример за единственно правильный.
В том, что модель — это исключительно бизнес-логика.
Домен — это бизнес-логика. Но "объект, предоставляющий информацию о домене" — это не только бизнес-логика. В описании Model из MVC Фаулер явно пишет: "all the data and behavior other than that used for the UI". Не "данные домена", а "все данные, кроме используемых исключительно для UI". В частности, поведение, отвечающее за работу с БД, тоже окажется здесь. Или за вызов сторонних сервисов. Или за логирование.
То, что model и object в ней никогда не употребляются как синонимы. Domain Model — это слой (layer).
Нет, это вы так придумали. Но ни в понятии Domain Model, ни в понятии MVC слово Model это не обозначает.
"bloated domain objects". Не модели, а объекты.
rpsv Автор
Окей, разберемся в терминах. Чуть выше я привел пример объект «деньги», который по моему мнению тоже модель (без поведения, просто данные). Вы говорите что это не модель, а объект домена — все верно?
Т.е. модели — это то что отражает объекты предметной области (неважно есть поведение или нет), а объекты домена — это вспомогательные сущности без поведения предметной области?
lair
Нет. В рамках Domain Model, как это понятие введено Фаулером, domain objects — это объекты, описывающие бизнес, как данные, так и поведение. А domain model — это совокупность этих объектов (и их связей).
Модели, множественное, в этой терминологии возможны только тогда, когда вам надо декомпоновать сложный бизнес на несколько более простых областей (то, что у Эванса называется bounded context).
А в рамках MVC, опять же, по Фаулеру, model — это все, что не UI.
rpsv Автор
Ну собственно когда я говорю модель — я имею ввиду domain object. А Domain Model — это по сути концепция/подход, а не сущность/объект. Вроде в этом ключе все и описано в статье, но в любом случае пройдусь еще раз, чтобы не было недопонимания. Спасибо!
lair
А другие люди понимают другое. На этом фоне ваш пост с заголовком "как должны выглядеть модели" и вступлением "все ли понимают что такое модель" выглядит неуместно. Потому что, внезапно, вы понимаете под словом "модель" что-то свое, но радостно провозглашаете свое мнение правильным.
rpsv Автор
Эээ чуть выше вы сами написали что в разных концепциях термин «модель» имеет разные значения (MVC и DM). Поэтому мое мнение НЕ не правильное, а отличающееся от концепции MVC и ложащееся в концепцию DM (domain objects).
Я и не провозглашал, что мое мнение истина в последней инстанции. Я лишь сказал что модели — это исключительно бизнес-логика. И это мнение правильное для меня, и абсолютно не ложится в термины MVC. Я эту идею транслирую, так же как Фаулер транслирует свою. Это разные точки зрения (да и по большому счету «о разном») и как-то сравнивать 2 термина из разных концепций странное занятия (естественно у Фаулера запас доверия куда бооооооольше, но это не значит что мое мнение ничего не стоит).
lair
Нет, потому что в концепции Domain Model под model понимается не то, что у вас.
Тогда в чем смысл вопроса "все ли до конца понимают, что эти слова означают?". Мы вот выяснили, что вы — не понимаете.
Проблема не в вашем мнении. Проблема в том, что ваш пост написан с позиции "я знаю, как правильно, и правильно — вот так (и только так)" — или, по крайней мере, читается, как написанный с такой позиции. При этом продвигаемые вами идеи — спорны.
rpsv Автор
Мы вроде выяснили что я понимаю не так как Вы, а ваша аргументация почему мое понимание не ложится в концепцию DM слабо выглядит.
Определение Фаулера (краткое):
Объекты отражающие сущности бизнес-логики (о чем в статье и речь). Если вы это определение воспринимаете иначе, то это уже проблема восприятия, а не формулировок.
lair
Не так, как я, не так, как MVC, и не так, как Domain Model.
Еще раз. У Фаулера Domain Model и domain object — это разные вещи (одно — часть другого). А у вас — одно и то же.
rpsv Автор
«Модель» != «Domain model», а «Модель» = «Domain object». Я вроде уже раза 4-5 это обозначил.
lair
Ну так это и есть ваше собственное понимание. В рамках Domain Model у Фаулера нет никакой другой модели, кроме Domain Model.
rpsv Автор
Как мы славно прыгаем по концепциям и терминам. Я для этого вначале статьи специально написал, что «модель — это бизнес-логика». Видимо чтобы было понятно для вас слово модель везде надо было заменить на «domain object», тогда бы вопросов не возникло?
Причем когда мы говорим про AR, такого вопроса почему то не возникает, и по-умолчанию понятно что является моделью. Хм…
lair
Ну да. Это выбранное вами определение, которое вы навязываете, как правильное.
… начиная с заголовка поста, да.
Я просто не говорю про AR.
rpsv Автор
Я как раз и обозначил вначале статьи что подразумевается под моделью. Потому что иначе пришлось бы рядом со словом модель писать пояснения для всех концепций (в рамках MVC — модель, в рамках DM — domain object и т.д.).
Занятное чтиво бы получилось. И наверняка нашелся бы тот (или какая-либо концепция), чье понимание я не отразил. Так что слово «модель» в рамках данной статьи, отражает именно то, что написано в статье.
Никогда бы не подумал что краткий референс на Domain Model сломает до такой степени понимание.
lair
Угу.
Не надо так.
Потому что вы начинаете с захода "сейчас я объясню, что такое модель", потом пишете "самое правильное определение для модели", а потом выясняется, что это просто вы подразумеваете под моделью.
Дело не в референсе, а в том, что вы подаете свое мнение как "самое правильное".
rpsv Автор
А почему «пишу самое правильное определение» это не тождественно равно «подразумеваю под моделью»? Синонимы как по мне.
Дак а я не увидел опровержений. Модель — это все что касается бизнес-логики. У Фаулера (с ваших слов) Модель — это все что не UI. Чем мое определение хуже, чем у Фаулера?
Касаемо DDD — термина модели нет, есть Агрегат, Сущность, VO — что по факту является детализацией/конкретизацией термина «модель»
lair
Нет, не синонимы. "Самое правильное определение" — это утверждение, а не мнение. А "я подразумеваю под моделью" — это введение определения, использующегося в посте, но не за его пределами.
Тем, что оно сужает Фаулеровское и заставляет людей, работающих с MVC, думать, что они делают что-то неправильно.
Нет, не является. Это вы вводите термин "модель", который является, по вашим словам, их обобщением. Можно ли их обобщать — вопрос отдельный.
VolCh
domain object — это любой из объектов, составляющих Domain Model: сущность, объект-значение, даже инстанс сервиса.
ActiveRecord можно назвать моделью, но моделью какой-то сущности, именно сущности — части общей доменной модели.
А тому, кто ввёл в обиход class User extends Model, я б не советовал встречать со мной на какой-нибудь афтерпати :)