О наблюдении дерева выполнения взаимозависимых моделей в MVC

Я разработал на Yii Framework некоторое время (4 месяца), и до сих пор я сталкивался с некоторыми проблемами с MVC, которые я хочу поделиться с опытными разработчиками там. Я расскажу об этих проблемах, указав уровни сложности.

[Уровень 1] Форма CR (создать обновление). Во-первых, у нас много форм. Каждая форма сама по себе является моделью, поэтому каждый из них имеет некоторые правила валидации, некоторые атрибуты и некоторые операции для выполнения атрибутов. Во многих случаях каждая из этих форм выполняет как обновление, так и создание записей в db с использованием одного объекта активной записи.
- > Итак, на этом уровне сложности форма должна

  • при открытии

    • сможет отображать db-дружественные данные из db в удобном для человека способом

    • может отображать все поля формы с атрибутами активного объекта записи. Добавление, удаление, изменение столбцов из таблицы db влияет на отображение формы.

  • при сохранении, возможность форматировать дружественные для человека данные для дружественных данных, прежде чем получать данные

  • при проверке, с возможностью выполнения основных проверок, выполняемых активным объектом записи, он также должен выполнять другие проверки для выполнения некоторых бизнес-правил.

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

[Уровень 2] Расширенная форма CR. Форма, которая может выполнять создание/обновление записей из разных таблиц сразу. Не только то, будет ли форма создавать/обновлять одну из своих записей, иногда может зависеть от других условий (более бизнес-правил), поэтому форма может иногда обновлять записи в таблице A, B, но не D, а иногда обновлять записи в A, D, но не B - > Итак, на этом уровне сложности мы видим, что форма должна:

  • сможет удовлетворить [Уровень 1]

  • иметь возможность условно создавать/обновлять определенные записи, условно создавать/обновлять определенные столбцы определенных записей.

[Уровень 3] Дерево моделей. Роль формы в приложении - это, во многом, порт, который позволяет пользователю взаимодействовать с вашим приложением. Чтобы удовлетворить запросы, этот порт будет взаимодействовать со многими другими объектами, которые, в свою очередь, взаимодействуют со многими другими объектами. Некоторые из этих объектов можно рассматривать как модели. Активная запись - это модель, но Mailer также может быть моделью, поэтому RobotArm. Эти модели используют друг друга для удовлетворения запроса пользователя. Каждая модель может выполнять свою собственную операцию, и все дерево должно иметь возможность откатывать любые изменения, сделанные в случае ошибки/сбоя.

Кто-нибудь там сталкивался или смог решить эти проблемы?

Я придумал много вещей, таких как инкапсуляция атрибутов модели в объектах ModelAttribute, чтобы решить их существование на всех уровнях клиента, сервера и db.

Я также подумал, что мы должны дать дереву моделей Observer наблюдать и сообщать наблюдаемым моделям об изменениях отката при возникновении ошибок. Но что, если могут существовать несколько наблюдателей, что если node использует своего родительского наблюдателя, но дает своим детям еще один наблюдатель.

Инженеры, разработчики, Rails, Yii, Zend, ASP, JavaEE, любые ребята из MVC, пожалуйста, присоединяйтесь к этой дискуссии ради науки.

- Обновить до ответа teresko: ---
@teresko Я на самом деле намеревался включить сервисы в выполнение внутри единицы работы и заставить Рабочую группу не беспокоиться о новых/обновленных/удаленных. Каждый объект внутри единицы работы будет отвечать за свое состояние и должен будет выполнять свои собственные commit() и rollback(). После возникновения ошибки блок работы откатит все изменения от самого нового зарегистрированного объекта до самого старого зарегистрированного объекта, поскольку мы имеем дело не только с базой данных, у нас могут быть почтовые программы, издатели и т.д. Если иначе, дерево успешно выполняется, мы вызываем commit() от самого старого зарегистрированного объекта к самому новому зарегистрированному объекту. Таким образом, почтовая программа может сохранить почту и отправить ее на фиксацию.

Использование Data Mapper - отличная идея, но нам все равно нужно убедиться, что столбцы в db соответствуют данным mapper и объекту домена. Более того, расширенная форма CR или модель, которая имеет свои атрибуты в зависимости от других моделей, должна соответствовать их атрибутам с точки зрения проверки и типа данных. Может быть, атрибут может быть объектом и отправлен из модели в модель? Атрибут также может определить, если он был изменен, какая проверка должна быть выполнена на нем, и как он может быть удобным для пользователя, удобным для приложений и дружественным к db. Любое обновление схемы db повлияет на этот атрибут и, тем самым, бросая исключения, требующие от разработчиков внести изменения в систему, чтобы удовлетворить это изменение.

Ответ 1

Причина

Корень вашей проблемы - неправильное использование активной записи. AR предназначен для простых объектов домена только с базовыми операциями CRUD. Когда вы начинаете добавлять большую логику проверки и отношения между несколькими таблицами, шаблон начинает разрываться.

Активная запись в лучшем случае является незначительным нарушением SRP для простоты. Когда вы начинаете накапливать обязанности, вы начинаете налагать суровые наказания.

Решение (s)

Уровень 1:

Лучшим вариантом является отдельная логика бизнеса и хранения. Чаще всего это делается с помощью объекта домена и data mappers:

  • Объекты домена (в других материалах, также известных как объекты бизнес-объекта или объекта модели домена) имеют дело с проверкой и конкретными бизнес-правилами и совершенно не знают, как (или даже "если" ) данные в них сохранялись и извлекались. Они также позволяют иметь объект, который не связан непосредственно с структурами хранения (например, таблицами БД).

    Например: у вас может быть объект домена LiveReport, который представляет текущие данные о продажах. Но в БД он может не иметь конкретной таблицы. Вместо этого он может обслуживаться несколькими картографами, которые собирают данные из Memcache, базы данных SQL и некоторого внешнего SOAP. А логика экземпляра LiveReport полностью не связана с хранилищем.

  • Датчики данных знают, куда помещать информацию из объектов домена, но они не проверяют целостность данных или проверку целостности данных. Мысль, что они могут обрабатывать исключения, которые конусы от абстракций хранения низкого уровня, например, нарушение ограничения UNIQUE.

    Датчики данных также могут выполнять транзакцию, но если для нескольких объектов домена требуется выполнить одну транзакцию, вы должны добавить, чтобы добавить Единицу работы (подробнее об этом ниже).

    В более сложных/сложных случаях карты данных могут взаимодействовать и использовать DAOs и построители запросов. Но это больше для ситуации, когда вы пытаетесь создать ORM-подобную функциональность.

    Каждый объект домена может иметь несколько картографов, но каждый обработчик должен работать только с определенным классом объектов домена (или подклассами одного из них, если ваш код придерживается LSP). Вы также должны признать, что объект домена и коллекция объекта домена являются двумя отдельными предметами и должны иметь отдельные сопоставления.

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

Другим усовершенствованием, которое могло бы облегчить ваш текущий беспорядок, было бы предотвращение протекания логики приложения на уровне презентации (чаще всего - контроллер). Вместо этого вы в значительной степени выиграете от использования служб, которые содержат взаимодействие между mappers и объектами домена, создав таким образом API-интерфейс public-ish для вашего модельного слоя,

В основном, сервисы, которые вы инкапсулируете полными сегментами вашей модели, которые могут (в реальном мире - с небольшими усилиями и настройками) повторно использоваться в разных приложениях. Например: Recognition, Mailer или DocumentLibrary будут все службы.

Кроме того, я думаю, что не должен, что не все службы должны содержать объект и карты домена. Хорошим примером может служить ранее упомянутый Mailer, который может использоваться либо напрямую контроллером, либо (что более вероятно) другой службой.

Уровень 2:

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

Как я вижу, есть два способа приблизиться к этому:

  • Quick'n'Dirty

    Если что-то изменилось, просто обновите все это...

    То, что я предпочитаю, - это ввести переменную checksum в объекте домена, которая содержит хэш из всех переменных объекта домена (конечно, за исключением checksum it self).

    Каждый раз, когда картографу предлагается сохранить объект домена, он вызывает метод isDirty() на этом объекте домена, который проверяет, изменились ли данные. Тогда картограф может действовать соответствующим образом. Это также с некоторыми настройками можно использовать для графиков объектов (если они не являются обширными, и в этом случае вам может понадобиться рефакторинг в любом случае).

    Кроме того, если ваш объект домена фактически сопоставляется с несколькими таблицами (или даже с разными формами хранения), может быть разумным иметь несколько контрольных сумм для каждого набора переменных. Так как сборщик уже написан для конкретных классов объекта домена, это не укрепит существующую связь.

    Для PHP вы найдете несколько примеров кода в этом ansewer.

    Примечание:, если ваша реализация использует DAO для изоляции объектов домена от карт данных, тогда логика проверки на основе контрольной суммы будет перенесена в DAO.

  • Единица работы

    Это "отраслевой стандарт" для вашей проблемы, и есть целая глава (11), посвященная этому в книге PoEAA.

    Основная идея заключается в том, что вы создаете экземпляр, который действует как контроллер (в классическом, а не в смысле MVC слова) между вами объектами домена и данными.

    Каждый раз, когда вы изменяете или удаляете объект домена, вы сообщаете об этом подразделению. Каждый раз, когда вы загружаете данные в объект домена, вы запрашиваете Unit of Work для выполнения этой задачи.

    Есть два способа сообщить Unit of Work об изменениях:

    • регистрация вызывающего абонента: объект, который выполняет изменение, также информирует Отдел работы
    • регистрация объекта: измененный объект (обычно из сеттера) информирует Единицу работы, что она была изменена.

    Когда все взаимодействие с объектом домена завершено, вы вызываете метод commit() в Единице работы. Затем он находит необходимые карты и сохраняет все измененные объекты домена.

Уровень 3:

На этом этапе сложности единственной жизнеспособной реализацией является использование Единицы работы. Он также будет отвечать за инициирование и совершение транзакций SQL (если вы используете базу данных SQL) с соответствующими предложениями отката.

P.S.

Прочитайте книгу "Шаблоны архитектуры корпоративных приложений". Это то, что вам отчаянно нужно. Он также исправит неправильное представление о шаблонах дизайна MVC и MVC, которые вы приобрели с помощью Rails-подобных фреймворков.