О наблюдении дерева выполнения взаимозависимых моделей в MVC
Я некоторое время разрабатывал Yii Framework (4 месяца), и до сих пор я столкнулся с некоторыми проблемами с MVC, которыми хочу поделиться с опытными разработчиками. Я представлю эти проблемы, перечислив их уровни сложности.
[Уровень 1] Форма CR(создание обновления). Во-первых, у нас много форм. Каждая форма сама по себе является моделью, поэтому у каждой есть свои правила проверки, некоторые атрибуты и некоторые операции над атрибутами. Во многих случаях каждая из этих форм выполняет как обновление, так и создание записей в БД с использованием одного активного объекта записи.
-> Так что на этом уровне сложности форма должна
когда открыт,
быть в состоянии отобразить дружественные к БД данные из БД в удобной для человека форме
уметь отображать все поля формы с атрибутами активного объекта записи. Добавление, удаление и изменение столбцов из таблицы БД должно влиять на отображение формы.
при сохранении уметь форматировать удобные для человека данные в удобные для БД данные, прежде чем получать данные
при проверке уметь выполнять базовые проверки, выполняемые активным объектом записи, он также должен выполнять другие проверки для выполнения некоторых бизнес-правил.
если проверка не удалась, можно откатить изменения, внесенные в атрибут, а также изменения, внесенные в базу данных, и представить пользователю его первоначально введенные данные.
[Уровень 2] Расширенная форма CR. Форма, которая может создавать / обновлять записи из разных таблиц одновременно. Мало того, что форма будет создавать / обновлять одну из своих записей, иногда может зависеть от других условий (больше бизнес-правил), поэтому форма может иногда обновлять записи в таблице A,B, но не в D, а иногда обновлять записи в A,D, но не B -> Итак, на этом уровне сложности мы видим, что форма должна:
быть в состоянии удовлетворить [Уровень 1]
уметь условно создавать / обновлять определенные записи, условно создавать / обновлять определенные столбцы определенных записей.
[Уровень 3] Древо моделей. Роль формы в приложении во многом является портом, который позволяет пользователю взаимодействовать с вашим приложением. Для удовлетворения запросов этот порт будет взаимодействовать со многими другими объектами, которые, в свою очередь, взаимодействуют со многими другими объектами. Некоторые из этих объектов можно рассматривать как модели. Active Record - это модель, но Mailer также может быть моделью, как и RobotArm. Эти модели используют друг друга, чтобы удовлетворить запрос пользователя. Каждая модель может выполнять свои собственные операции, и все дерево должно иметь возможность откатывать любые изменения, сделанные в случае ошибки / сбоя.
Кто-нибудь там сталкивался или смог решить эти проблемы?
Я придумал множество вещей, таких как инкапсуляция атрибутов модели в объектах ModelAttribute, для решения их существования на всех уровнях клиента, сервера и базы данных.
Я также подумал, что мы должны дать дереву моделей Наблюдатель для наблюдения и уведомить наблюдаемые модели для отката изменений при возникновении ошибок. Но что, если может существовать несколько наблюдателей, что если узел использует наблюдателя своего родителя, но предоставляет своим дочерним элементам других наблюдателей.
Инженеры, разработчики, Rails, Yii, Zend, ASP, JavaEE, любые ребята из MVC, пожалуйста, присоединяйтесь к этой дискуссии ради науки.
- Обновление ответа Тереско: ---
@teresko На самом деле я намеревался включить сервисы в выполнение внутри единицы работы, чтобы единица работы не беспокоилась о новых / обновленных / удаленных. Каждый объект внутри единицы работы будет нести ответственность за свое состояние и должен будет реализовывать свои собственные commit() и rollback(). При возникновении ошибки единица работы выполнит откат всех изменений от самого нового зарегистрированного объекта до самого старого зарегистрированного объекта, так как мы имеем дело не только с базой данных, у нас могут быть почтовые программы, издатели и т. Д. В противном случае дерево успешно выполняется., мы вызываем commit() от самого старого зарегистрированного объекта до самого нового зарегистрированного объекта. Таким образом, почтовик может сохранить почту и отправить ее при коммите.
Использование Data Mapper - отличная идея, но мы все равно должны убедиться, что столбцы в БД соответствуют Data Mapper и объекту домена. Кроме того, расширенная форма CR или модель, которая имеет свои атрибуты в зависимости от других моделей, должна соответствовать своим атрибутам с точки зрения валидации и типа данных. Так, может быть, атрибут может быть объектом и доставляться от модели к модели? Атрибут также может сказать, был ли он изменен, какую проверку следует выполнить с ним, и как он может быть удобен для человека, дружественен к приложениям и дружественен к базе данных. Любое обновление схемы БД повлияет на этот атрибут и, таким образом, вызовет исключения, которые требуют от разработчиков внесения изменений в систему, чтобы удовлетворить это изменение.
1 ответ
Причина
Корень вашей проблемы - неправильное использование шаблона активной записи. AR предназначен для простых доменных объектов с только основными операциями CRUD. Когда вы начинаете добавлять большое количество логики проверки и отношений между несколькими таблицами, шаблон начинает разрываться.
Активная запись, в лучшем случае, является незначительным нарушением SRP, ради простоты. Когда вы начинаете накапливать обязанности, вы начинаете подвергаться суровым наказаниям.
Решение (s)
1-й уровень:
Лучший вариант - это отдельная логика бизнеса и хранения. Чаще всего это делается с помощью доменных объектов и картографов данных:
Доменные объекты (в других материалах, также называемых объектами бизнес-объектов или моделей доменов) имеют дело с проверкой и конкретными бизнес-правилами и совершенно не знают, как (или даже "если") данные в них были сохранены и извлечены. Они также позволяют иметь объект, который не связан напрямую со структурами хранения (такими как таблицы БД).
Например: у вас может быть
LiveReport
доменный объект, который представляет текущие данные о продажах. Но он может не иметь конкретной таблицы в БД. Вместо этого он может обслуживаться несколькими мапперами, которые объединяют данные из Memcache, базы данных SQL и некоторого внешнего SOAP. ИLiveReport
Логика экземпляра совершенно не связана с хранилищем.Устройства отображения данных знают, куда помещать информацию из объектов домена, но они не выполняют никакой проверки или проверки целостности данных. Мысль, что они могут быть в состоянии обрабатывать исключения, которые происходят из низкоуровневых абстракций хранения, например, нарушение
UNIQUE
ограничение.Устройства отображения данных также могут выполнять транзакции, но, если для нескольких доменных объектов требуется выполнить одну транзакцию, вам следует добавить единицу работы (подробнее об этом ниже).
В более сложных / сложных случаях преобразователи данных могут взаимодействовать и использовать DAO и построители запросов. Но это больше для ситуации, когда вы стремитесь создать ORM-подобную функциональность.
Каждый объект домена может иметь несколько сопоставителей, но каждый сопоставитель должен работать только с определенным классом объектов домена (или подклассом из одного, если ваш код придерживается LSP). Вы также должны признать, что объект домена и набор объектов домена - это две разные вещи и должны иметь отдельные средства отображения.
Кроме того, каждый объект домена может содержать другие объекты домена, так же как каждый преобразователь данных может содержать другие преобразователи. Но в случае картостроителей это гораздо более предпочтительный вопрос (мне это сильно не нравится).
Другим улучшением, которое может облегчить ваш текущий беспорядок, было бы предотвращение утечки логики приложения на уровне представления (чаще всего - контроллер). Вместо этого вы в значительной степени выиграете от использования сервисов, которые содержат взаимодействие между мапперами и объектами домена, создавая, таким образом, общедоступный API для уровня модели.
По сути, сервисы, которые вы инкапсулируете в полные сегменты вашей модели, могут (в реальном мире - с небольшими усилиями и корректировками) использоваться повторно в различных приложениях. Например: Recognition
, Mailer
или же DocumentLibrary
будет все услуги.
Кроме того, я не думаю, что не все службы должны содержать объект домена и средства отображения. Неплохим примером будет упомянутый ранее Mailer
, который может использоваться либо непосредственно контроллером, либо (что более вероятно) другим сервисом.
Уровень 2:
Если вы перестанете использовать шаблон активной записи, это станет довольно простой проблемой: вам нужно убедиться, что вы сохраняете только данные из тех объектов домена, которые фактически изменились с момента последнего сохранения.
На мой взгляд, есть два пути к этому:
Quick'n'Dirty
Если что-то изменилось, просто обновите все это...
Способ, который я предпочитаю, это ввести
checksum
переменная в объекте домена, которая содержит хэш от всех переменных объекта домена (конечно, за исключениемchecksum
сам).Каждый раз, когда маперу предлагается сохранить объект домена, он вызывает метод
isDirty()
на этом доменном объекте, который проверяет, изменились ли данные. Тогда картограф может действовать соответственно. Это также, с некоторыми корректировками, может использоваться для графов объектов (если они не слишком обширны, в этом случае вам может понадобиться рефакторинг в любом случае).Кроме того, если ваш доменный объект действительно отображается в несколько таблиц (или даже в разные формы хранения), может быть целесообразно иметь несколько контрольных сумм для каждого набора переменных. Поскольку маппер уже написан для конкретных классов доменного объекта, это не усилит существующую связь.
Для PHP вы найдете несколько примеров кода в этом разделе.
Примечание. Если ваша реализация использует DAO для изоляции объектов домена от преобразователей данных, то логика проверки на основе контрольной суммы будет перенесена в DAO.
Это "отраслевой стандарт" для вашей проблемы, и в книге PoEAA есть целая глава (11).
Основная идея заключается в том, что вы создаете экземпляр, который действует как контроллер (в классическом, а не в MVC-смысле этого слова) между вашими объектами домена и преобразователями данных.
Каждый раз, когда вы изменяете или удаляете объект домена, вы информируете об этом единицу работы. Каждый раз, когда вы загружаете данные в объект домена, вы просите Unit of Work выполнить эту задачу.
Есть два способа сообщить об изменениях в Unit of Work:
- регистрация вызывающего абонента: объект, который выполняет изменение, также информирует единицу работы
- регистрация объекта: измененный объект (обычно из установщика) сообщает единице работы, что он был изменен
Когда все взаимодействие с доменным объектом завершено, вы вызываете
commit()
метод на единицу работы. Затем он находит необходимые преобразователи и хранит все измененные доменные объекты.
Уровень 3:
На данном этапе сложности единственной жизнеспособной реализацией является использование Unit of Work. Он также будет отвечать за инициирование и фиксацию транзакций SQL (если вы используете базу данных SQL) с соответствующими пунктами отката.
PS
Прочитайте книгу "Шаблоны корпоративной архитектуры приложений". Это то, что вам отчаянно нужно. Это также исправило бы неправильное представление о шаблонах проектирования, основанных на MVC и MVC, которое вы приобрели, используя Rails-подобные фреймворки.