!!! ВНИМАНИЕ DEPRECATED
Данная статья более неактуальна, потому что я успел сделать еще один скачок эволюции и перейти в сторону функциональщины, поэтому дальнейшие материалы будут выходить в рамках 2-х онлайн книг:
—
Предисловие
В процессе технического аудита (LVL UP) я часто сталкиваюсь с этой проблемой, которая делает код нечитабельным куском дерьма софтины… нет, все-таки, дерьма.
Если ваш проект больше маленького (от 3-х месяцев разработки и с дальнейшей поддержкой), вам будет это полезно:
В чем цимус
Я делю понятие fat (толстая) / smart (умная) / rich (богатая) Model и понятие obese (жирная) Model. Вот об этой разнице и поговорим.
Вкратце, Модель (Model) – это объект, который объединяет данные и логику какой-то сущности, которыми мы манипулируем в рамках нашего приложения.
Например, модель Пользователь
. У нее могут быть Пароль
и Email
(данные), а еще Пользователь устанавливает себе новый пароль (логика).
Fat (толстая) / Smart (умная) / Rich (богатая) Model (далее Rich Model или RM) – утрировано, это когда все модификации свойств модели происходят через вызов ее же методов.
// Rich model class User { // Props email: string password: string // Logic setNewPassword(newPassword: string): void { if (newPassword === this.pasword) { throw new Error("New password must not be the same") } this.password = newPassword } }
Slim (тонка) / Anemic (анемичная) Model – утрировано, это когда все модификации модели происходят через прямое присваивание ее свойств в других местах кода.
// Slim model class User { // Props email: string password: string } class UserService { setNewPassword(user: User, newPassword: string): void { if (newPassword === user.pasword) { throw new Error("New password must not be the same") } user.password = newPassword } }
Я всегда ЗА Rich Model, потому что она отлично соответствует принципам Tell Don’t Ask и Law of Demeter, которые я считаю основополагающими для сохранения принципов SOLID.
С таким определением Модели нет никаких проблем, но часто разработчики начинают связывать ее с Базой Данных (БД). Такая Модель превращается в Active Record и уже здесь начинают появляться проблемы (хотя, конечно, жить можно).
А еще хуже, когда Модель становиться Obese (жирной) – это когда туда начинают еще добавлять логику приложения.
Например, добавляют метод sendRegistrationConfirmation
, который отправляет email об успешной регистрации (и это не шутка, я часто такое видел).
Следующая стадия ожирения наступает, когда Модель начинает отвечать за другие Модели. Пример:
У Пользователя есть Визитка. При изменении ФИО у Пользователя, нужно поменять ФИО и в Визитке. Пользователю добавляют метод changeFIO
внутри которого подтягивают сущность Визитки и устанавливают у нее новое ФИО. То есть, теперь Пользователь модифицирует и сущности Визитки.
Это может быть нормально, если Визитка является «дочерней» / «подконтрольной» сущностью по отношению к Пользователю.
Вишенкой же на торте становиться то, что при изменении ФИО в Визитке, надо поменять ФИО Пользователя. И вот тут Визитке добавляют метод changeFIO
, который меняет и ФИО Пользователя…
Любу циклическую / двунаправленную логику очень тяжело поддерживать, понимать и гибко изменять.
SOLID? Не, не слышали
Короче, по итогу наша Модель (1) содержит данные, (2) свою бизнес-логику, (3) логику приложения, (4) отвечает за работу с БД (Active Record), (5) так еще и двунаправленную логику других Моделей.
Это тяжело писать, понимать, поддерживать, рефакторить и практически невозможно в нормальные сроки покрывать тестами.
Просто… пиздец… И самое смешное – это очень распространенный пиздец.
Так что делать?
Нам нужно вынести по максимуму из Модели, начнем:
Выносим логику приложения
Сначала убираем всю логику приложения (отправки мейлов / нотификаций, формирование jwt, запросы в сторонние сервисы) в Контроллер / Сервисы.
Да, это плохой вариант, но это первый шаг, который нужно сделать к светлому будущему.
Выносим БД
Чтобы отвязать БД от Моделей (уйти от паттерна Active Record), нам нужен Data Mapper (DM).
Паттерн Data Mapper берет 2 сущности и мапит их друг к другу. Например сущность Пользователя из вашего приложения и сущность Пользователя из БД / стороннего API (например Auth0) / чего угодно.
Именно DM позволяет качественно абстрагировать работу с источником данных (Data Access Layer – DAL).
Вариантов DM много, но вот самые распространенные:
- DAO – вариант, но гавенный
- Repository – а вот это уже тема (а если добавить Query Object (QO) – то просто агонь ?)
Сама концепция утверждает, что в Repository
можно использовать любое количество DAO, но в DAO нельзя использовать Repository, поэтому Repository находиться на более высоком слое абстракции, что дает ему преимущество.
Где посмотреть примеры Repository
:
- Библиотека DDDL для JS и TypeScript
- Еще для JS / TS: TypeORM, MirkoORM
- Для PHP Doctrine
- Для C# EF
Я вернусь к вопросу описания Repository
в отдельной статьей с примерами.
Подписывайтесь на телегу, чтобы не пропустить анонсы новостей.
P.S.
История с Репозиторием опциональная, в отличии от «выноса логики приложения» и «однонаправленной иерархии».
Однонаправленная иерархия
Чтобы поправить ситуацию с циклическими / двунаправленными зависимостями без СМС и регистрации надо…

Догадались?
Правильно! Сделать их однонаправленными, построив иерархию.

Суть заключается в том, что только методы User
могут менять Profile
напрямую и Visit Card
через Profile
. При этом ни Visit Card
, ни Profile
не имеют права менять User
.
В MVC
очень редко достаточно полноценно рассматривается этот вопрос (даже не знаю названия концепции из MVC
, которая устанавливала правила построения таких иерархий).
А вот в DDD (Light)
об этом написано много. И в частности, чтобы понять как правильно строить подобные деревья зависимостей сущностей надо изучить паттерны Entity
(сущность) и Aggregate
(агрегат).
Я уже почти дописал статьи на тему этих патернов и скоро выпущу их + делаю курс, где пошагово все расскажу. А пока смотрите вот это:
- Интерфейсы Entity
- Интерфейсы Aggregate
- Примеры Аггрегатов
Подписывайтесь на телегу, чтобы не пропустить анонсы новостей.
По итогу
Теперь в Модели у нас лежит только ее свойства, логика модификации этих свойств и при этом все Модели построены в однонаправленные иерархические деревья. Это чисто и приятно. Вся логика приложения и работа с БД ушла на другие слои. На такую сущность приятно смотреть и работать с ней.
Если что-то непонятно по статье, пишите в телегу (не стесняйтесь).
Теперь ждем статью «Плавненько от MVC к DDD Light. Часть 2. Диета жирного Контроллера.»