LINUX.ORG.RU

Где место HTTP в паттернах MV*?

 , , ,


1

2

Здравствуйте

Прочитал вдохновляющую статью про разные виды MV*. Осталась пара незакрытых вопросов.

Для начала выдержка из статьи, описывающая ответственность PresentationModel в одноименном паттерне. PresentationModel:

  • Содержит логику пользовательского интерфейса: Так же, как и презентер, PresentationModel содержит логику пользовательского интерфейса. Когда вы нажимаете на кнопку, это событие направляется в PresentationModel, которая затем решает, что с ним делать.
  • Предоставляет данные из модели для отображения на экране PresentationModel может преобразовывать данные из модели так, чтобы они были легко отображены на экране. Часто информация, содержащаяся в модели, не может непосредственно использоваться на экране. Вам, возможно, сначала потребуется преобразовать данные, их дополнить или собрать из нескольких источников. Это наиболее вероятно, когда у вас нет полного контроля над моделью. Например, если вы получаете данные от сторонних веб-сервисов или же из базы данных существующего приложения.
  • Хранит состояние пользовательского интерфейса Зачастую пользовательский интерфейс должен хранить дополнительную информацию, которая не имеет ничего общего с моделью. Например, какой элемент выбран в данный момент на экране? Какие ошибки валидации произошли? PresentationModel может хранить эту информацию в свойствах.

Вопрос 1. Куда мне засунуть http-запросы? Например, веб-страница раз в секунду опрашивает сервер на предмет изменений. Где расположить этот setTimeout? В PrsentationModel? В Model? Где-то отдельно?

Вопрос 2. View-ов (и, соттветственно их презентаций) я так понимаю, может быть много? Например, по одному View на каждую форму. А что с моделью? Одна модель на всё приложение или по модели на каждый View?

★★★★★

В твоём фреймворке уже есть сложившиеся практики. В случае с react'ом — модель обычно называется store и ровно одна на всё приложение, а http запихивается в action'ы либо саги.

x3al ★★★★★ ()

Запросы все идут в V, жаваскрипт никуда засовывать не надо, ему место в HTML.

Goury ★★★★★ ()
Ответ на: комментарий от x3al

В твоём фреймворке уже есть сложившиеся практики

А у меня его нет ¯\_(ツ)_/¯. Раньше писал на backbone и angular. Из них четкого понимания что куда совать не вынес. Сейчас использую Rivetsjs для View и vanilla-js для остального. Но не суть

модель обычно называется store и ровно одна на всё приложение

Тоже склоняюсь к такому решению. По крайней мере до тех пор, пока на одной странице не будут несколько абсолютно непересекающихся приложений)

http запихивается в action'ы либо саги

Я с реактом не знаком. Кажется он только View, а остальное на усмотрение разработчика? Экшены и саги это отдельные сущности специально для http?

makoven ★★★★★ ()
Ответ на: комментарий от makoven

Экшены и саги это отдельные сущности специально для http?

Это сущности, приделанные к redux'у, строго говоря не привязанные к реактам. Просто асинхронные вещи в View делают его жирным, а модель в виде store не умеет в HTTP (в бэкбонах — умеет). Ну и со всей асинхронностью на каждом шагу будет получается что то вид дёргает модель, то модель (после получения данных от сервера) — вид, что обычно запутывает код, поэтому они вынесены из собственно модели (и вида).

x3al ★★★★★ ()
Последнее исправление: x3al (всего исправлений: 1)
Ответ на: комментарий от x3al

Просто асинхронные вещи в View делают его жирным, а модель в виде store не умеет в HTTP (в бэкбонах — умеет)

Вот-вот. Если с событиями от кнопок всё понятно: они идут от View в Presenter или PresentationModel. А http куда девать.. Куда ни сунь, вроде как получится нарушение принципа единой ответственности

makoven ★★★★★ ()
Ответ на: комментарий от makoven

Ну, поэтому flux — не совсем классический MV*, но в нём всё очевидно и всё прекрасно работает без всяких нарушений единой ответственности.

x3al ★★★★★ ()
Ответ на: комментарий от x3al

В случае с react'ом — модель обычно называется store и ровно одна на всё приложение

Читаю про flux, и тут внезапно: «There should be many stores in each application»

makoven ★★★★★ ()
Ответ на: комментарий от makoven

Значит рано тебе ещё соваться в разработку, освой основы для начала.
Начни со спецификации HTML, потом изучи что такое HTTP.

Goury ★★★★★ ()

Например, по одному View на каждую форму. А что с моделью? Одна модель на всё приложение или по модели на каждый View?

Ну, как бы, из классического определения, следует, что модель одна

Активная модель — модель оповещает о произошедших в ней изменениях те представления, которые подписались на получение таких оповещений. Это позволяет сохранить независимость модели от контроллеров и представлений.

Классической реализацией концепции MVC принято считать версию именно с активной моделью.

Но по-идее, никаких препятствий, чтобы сделать их больше не вижу.

anonymous ()
Ответ на: комментарий от Goury

Не понял сути претензии. Можно подробнее что не так?

makoven ★★★★★ ()
Ответ на: комментарий от anonymous

Но по-идее, никаких препятствий, чтобы сделать их больше не вижу

Я тоже. Потому и спросил) Куда больше интересует вопрос с http

makoven ★★★★★ ()
Последнее исправление: makoven (всего исправлений: 1)

Куда мне засунуть http-запросы?

можно их реализовать отдельным классом, который оповещает контроллер, например

anonymous ()
Ответ на: комментарий от anonymous

Получится какой-то MVH* паттерн) Особенно когда http-запрос надо делать в ответ на нажатие кнопки. View делегирует событие презентеру, презентер - HTTPRequest-классу, тот - обратно презентеру

makoven ★★★★★ ()
Ответ на: комментарий от makoven

Особенно когда http-запрос надо делать в ответ на нажатие кнопки.

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

anonymous ()
Ответ на: комментарий от Goury

Я тоже не понял. жаваскрипт — ЯП, HTML — это язык гипертекстовой разметки, как можно засунуть одно в другое? Какие такие «основы» эдакой диковине научают?

linearisation ()
Ответ на: комментарий от linearisation

Дак этож клоун местный, советую не обращать внимания.

Kilte ★★★★★ ()
Ответ на: комментарий от makoven

В классическом flux — да. В redux — нет. Сейчас мейнстрим — redux.

x3al ★★★★★ ()
Ответ на: комментарий от linearisation

Яваскрипт является подмножеством спецификации HTML

Goury ★★★★★ ()
Ответ на: комментарий от x3al

А как принято расширять такую модель, чтобы потом не погибнуть в путанице из кучи свойств?

Vit ★★★★★ ()
Ответ на: комментарий от x3al

А в чем профит по сравнению с привычной моделью в виде класса?

makoven ★★★★★ ()
Ответ на: комментарий от anonymous

Обработка нажатия то конечно дело контроллера: модель изменить и на представление повлиять. А вот http запросы в контроллере, кмк не совсем уместно. Слишком много ответственности для контроллера. И тестировать его будет проблематично, с http запросами-то

makoven ★★★★★ ()
Ответ на: комментарий от makoven

Ты можешь расширить MVC, добавив абстрактную сущность Component (я надеюсь ты не очередной фрейм решил делать?). И контроллер будет делегировать кучи ненужных обработок Component. Часто встречался в жизни с «жирными» контроллерами и их мешаниной из многих действий – элементарно – валидация формы, такой специфической нудятине не место в контроллере, его прозрачность не должна уступать прозрачности модели.

znenyegvkby ()
Ответ на: комментарий от makoven

В чём профит иммутабельного сериализуемого стейта? Зависит от приложения, обычно это пользуют для девтулзов с машиной времени, персистентности (сохранение части/целого стейта в localstorage/indexeddb/whatever), отмены/повторения, воспроизводимых логов действий юзера, возможности воспроизвести какое-то действие где-то ещё (в соседнем табе, на сервере+клиенте через websocket одновременно) и прочих вещей, которые получаешь практически бесплатно. Всё это никак не привязано к фреймворку (сам redux — ~4кб кода без зависимостей), но придумано для реактообразных.

Если всё это не нужно — есть тот же mobx, который заметно менее многословен.

x3al ★★★★★ ()
Ответ на: комментарий от Kilte

Там идеология. А я спрашивал как делают структуру дерева чтобы поноса не получилось по мере усложнения..

Vit ★★★★★ ()
Ответ на: комментарий от Vit

А в чём проблема? Можешь представлять это как куча сторов, объединённых в один объект. Там уже сам решаешь, как тебе удобнее. Обычно рекомендуют сильно не углубляться. Чем меньше уровень вложенности, тем должно быть легче. Не следует дублировать данные, вместо этого лучше хранить идентификаторы там, где требуется копия. Ну и всё в таком духе. Есть ещё https://github.com/tommikaikkonen/redux-orm Сам не пробовал, но выглядит интересно.

Kilte ★★★★★ ()
Ответ на: комментарий от Kilte

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

Vit ★★★★★ ()

На каждом уровне триады может быть своя отдельная триада.

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

Допустим, в модели нужно отдать карточку товара в виде JSON-объекта. Модель в свою очередь может запросить микро-слой у сервера. То-же самое может сделать и C и V. V может запросить данные напрямую или через подслой MVC и потом полученные данные отрендерить в шаблон или делегировать другому подслою MVC.

Приоритеты должны быть имхо в сторону упрощения и понижения сложности, внесение определенности. Это ведь просто паттерн или даже доктрина, а не догма.

Смысл основной в том, что каждый по своему туп (мало знает или не берет лишнего) на своем уровне.

Модель тупа - знает как извлекать данные, контроллер туп - имеет представления как извлекать данные без модельной конкретики, но знает достаточно как и кому это дело делегировать. Словом, отвечает за процесс принятия решений на более высоком уровне.

V - ленивец и просто ждет, когда ему дадут разжеванные до безобразия данные и знает что с ними дальше делать - например из JSON сделать HTML. Размышляет мало - плоская логика. Может принимать некоторые решения с целью дальнейшего упрощения в силу своей компетенции без особых разветвлений...

anonymous ()
Ответ на: комментарий от anonymous

На каждом уровне триады может быть своя отдельная триада.

А как их друг друга вкладывать? Что к чему прикреплять? M, V и C это ж просто 3 отдельных объекта

Приоритеты должны быть имхо в сторону упрощения и понижения сложности, внесение определенности

Куда бы вы поместили XHR в такой ситуации?: при нажатии кнопки посылается команда на сервер. Полученный с сервера ответ меняет M и отображается в V.

makoven ★★★★★ ()
Последнее исправление: makoven (всего исправлений: 2)
Ответ на: комментарий от Vit

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

anonymous ()
Ответ на: комментарий от makoven

А как их друг друга вкладывать? Что к чему прикреплять? M, V и C это ж просто 3 отдельных объекта

это 3 сущности, количество объектов так то не ограничено.

при нажатии кнопки посылается команда на сервер. Полученный с сервера ответ меняет M и отображается в V.

запили сервисный объект для работы с сетью с двумя потоками данный, один в виде событий от контроллера в этот объект, и необязательно от контроллера, а второй из сервисного объекта в виде экшенов на изменение стейта прямо в какой нибудь ридэкс, главное что бы эти 2 потока были асинхронны и тогда совершенно пофиг, что там внутри этого сервисного объекта, ajax, long polling, вебсокеты, канал связи между разными вкладками через локал сторедж, postMessage... или сразу все вместе.

TDrive ★★★★★ ()
Ответ на: комментарий от Vit

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

Например в rudex на каждый стор из этого одноуровневого набора сторов делается свой reducer который отвечает за изменение только этого стора. Как это может превратиться в понос?

TDrive ★★★★★ ()

Чтоб два раза не вставать (чтобы новую тему не создавать). У меня вот тоже вопрос есть.

1) Обычный подход с коллбеками в коллбеках, коллбеках, коллбеках.. Ну вы поняли.

2) На событиях, каждый любой компонент тупо бросает и слушает ивенты. Эдакий глобальный обзервер.

Что в первом варианте, что во втором — всеравно приходишь к куче говна, ибо взаимосвязи сложные.

А есть подход чтобы действительно по полочкам раскладывалось и аккуратно лежало вне зависимости от объемов и сложности приложения?

deep-purple ★★★★★ ()
Ответ на: комментарий от TDrive

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

Ок. Контроллер делает http-запросы (пусть и серез сторонню приблуду, не суть). Впринципе, я тоже к этому склоняюсь. Разве что в MVP P-это по определению «логика интерфейса» и совершать оттуда иные действия как-то не с руки

и необязательно от контроллера

А откуда еще?

а второй из сервисного объекта в виде экшенов на изменение стейта прямо в какой нибудь ридэкс

То-есть в стор, то-есть в модель, по факту?

Выходит, C получает евент от V делает http-запрос и изменяет M

makoven ★★★★★ ()
Ответ на: комментарий от deep-purple

В маленьком проектике делал без асинхронщины. Для чего-то большого не пойдет. Зато тестируется отлично, без асинхронных тестовый либ )

// model.js
function Timer_model(){}

Timer_model.prototype.new_time = function(sec){
  do_the_stuff();
  this.on_timer_change(); // этот метод не существует. Будет определен в V
};
// view.js
function Timer_view(){}

Timer_view.prototype.set_model = function(model){
  model.on_timer_change = this.render_timer.bind(this);
}
makoven ★★★★★ ()
Последнее исправление: makoven (всего исправлений: 2)
Ответ на: комментарий от makoven

Не. Тут те же проблемы будут что и с ивентами, даже хуже, ивенты «слышны» отовсюду, а это — местечковые костылики.

deep-purple ★★★★★ ()
Ответ на: комментарий от deep-purple

ивенты «слышны» отовсюду

Как часто это нужно? Подозреваю, процентах в 95 случаев: один эмитер — один подписчик

местечковые костылики

На таких костыликах весь веб работал. До изобратения addEventListener )

makoven ★★★★★ ()
Последнее исправление: makoven (всего исправлений: 1)
Ответ на: комментарий от makoven

Ок. Контроллер делает http-запросы (пусть и серез сторонню приблуду, не суть).

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

А откуда еще?

пофиг откуда, из других каких нибудь сервисных объектов например.

Выходит, C получает евент от V делает http-запрос и изменяет M

для начала mvc это паттерн для построения UI, контроллер в этом паттерне отвечает за логику на действия пользователя. Запросы на сервер, работа с какими нибудь сторонними апи это все к mvc не относится.

Если ты рассматриваешь сервер как апи со строгой логикой запрос-ответ то можно делать запросы из контроллера и результатами апдейтить модель.

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

TDrive ★★★★★ ()
Ответ на: комментарий от TDrive

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

Ок. Спасибо

поток от сервера подключается к модели, а поток в сторону сервера идет от контроллера в случае если это действия пользователя

Получается, что поллинг сервера мне надо засунуть в модель?

makoven ★★★★★ ()
Ответ на: комментарий от makoven

Получается, что поллинг сервера мне надо засунуть в модель?

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

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

TDrive ★★★★★ ()
Ответ на: комментарий от makoven

Как часто это нужно? Подозреваю, процентах в 95 случаев: один эмитер — один подписчик

Как правило, в сложных приложениях есть зависимые данные. Делать их такими костылями — то ещё удовольствие.

x3al ★★★★★ ()
Ответ на: комментарий от TDrive

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

TDrive ★★★★★ ()
Ответ на: комментарий от makoven

А как их друг друга вкладывать? Что к чему прикреплять? M, V и C это ж просто 3 отдельных объекта

hint: model.getData ---> any data source/provider <--- black box: может быть чем угодно, в том числе MVC (пример вложения). Например REST сервис.

Вообще, как правило в XHR передается callback для реагирования - это значительно упрощает обход: можно например апдейтить на прямую или через subscriber.notify(ev) или даже model.notify(ev)

Нужно выбирать более простую и понятную реализацию с минимальной неопределенностью. Это возможно, когда каждый делает свою работу - do one job. В данном случае M может обратится к слою, который отвечает за XHR, а тот в свою очередь тоже имеет свой слой MVC, только более узкоспециализированный... хотя, тут возможно упростить, не выходя за пределы первой M

Куда бы вы поместили XHR в такой ситуации?: при нажатии кнопки посылается команда на сервер. Полученный с сервера ответ меняет M и отображается в V.

По ситуации, куда проще делегировать: скорее в M, но через helper или C может подсказать способ. Короче: делегирование, изоляция в слоях (в разумны пределах) Насчет ответ меняет M - это вариант PubSub как правило. M должно быть все равно кто подписчики. Это как правило C, но может быть запросто и V - опять же зависит по разному. Лучше C т.к. он пусть решает как реагировать на изменения.

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

Еще один момент: не забывайте, что js это уровень V (с точки зрения сервера) в котором свои слои MVC, но с другой стороны он и C в целом с точки зрения например browser

anonymous ()

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

anonymous ()
Ответ на: комментарий от anonymous

То-есть M должна описывать состояние UI, а состояние самой програмы (предметной области) должно быть где-то отдельно?

makoven ★★★★★ ()
Ответ на: комментарий от makoven

То-есть M должна описывать состояние UI

Да, но необязательно напрямую. Например, тот же чекбокс - если в модели хранится информация о состоянии чекбокса, это не значит, что там прямо есть некоторое булево значение isChecked. Это значит, что по модели можно это isCheked узнать. Например, в модели хранится, на каком мы шаге заполнения формы, а уже сама вьюха или еще какой элемент (в зависимости от конкретного паттерна, например в рассматриваемом в оп-посте случае это будет как раз presentationModel) по номеру шага знает какие поля должны быть активны, где должны быть какие галочки отмечены и т.п..

а состояние самой програмы (предметной области) должно быть где-то отдельно?

Смотря что понимать под «состоянием программы». Вот «номер шага при заполнении формы» (допустим, форма из нескольких страниц) - это то самое состояние, которое мы рассматриваем и про работу с которым mv* паттерны, именно его содержит М. Оно отличается, понятно, от каких-то данных получаемых из внешних сервисов (вроде бд), тут разница должна быть понятна интуитивно. И кто именно, например, ходит в бд (или делает хттп запросы) и как - это уже вне паттерна. Ну, наверное, вьюха этого не делает (непонятно как вьюхе лезть ко внешним сервисам, это бы весьма странно смотрелось), а вот остальные элементы вполне могут, это уж как сам организуешь.

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.