Почему я больше не использую MVC-фреймворки

http://www.infoq.com/articles/no-more-mvc-frameworks
  • Перевод


Уважаемые хабравчане.

Поскольку дискуссия вокруг статьи идет весьма активно, Жан-Жак Дюбре (он читает комментарии) решил организовать чаты в gitter.

Вы можете пообщаться с ним лично в следующих чатах:
https://gitter.im/jdubray/sam
https://gitter.im/jdubray/sam-examples
https://gitter.im/jdubray/sam-architecture

Также автор статьи разместил примеры кода здесь: https://bitbucket.org/snippets/jdubray/

По поводу кода он оставил следующий комментарий:
I don't code for a living, so I am not the best developer, but people can get a sense of how the pattern works and that you can do the exact same thing as React + Redux + Relay with plain JavaScript functions, no need for all these bloated library (and of course you don't need GraphQL).

Опубликовано с большого одобрения автора и согласия портала infoq.com. Надеюсь, мои языковые навыки оправдают оказанное автором доверие.

Худшее в моей работе на сегодняшний день, это проектирование API для front-end разработчиков. Диалог с ними неизбежно разворачивается следующим образом:
Dev — итак, на этом экране нужны данные x, y и z. Не мог бы ты сделать API, которое вернет данные в формате {x:, y:, z: }

Я — ok

Я больше даже не спорю с ними. Проекты заканчивается ворохом различных API, привязанных к экранам, которые меняются очень часто, что “by design” требует изменений в API. Вы и глазом моргнуть не успеваете, а у вас уже куча API и для каждого необходимо поддерживать множество форматов и платформ. Сэм Ньюман даже начал формализовывать этот подход как BFF Pattern, предполагающий, что это нормально — разрабатывать отдельное API для каждого типа устройства, платформы и, естественно, каждой версии вашего приложения. Дэниэл Якобсон рассказывал, что Netflix был вынужден начать использовать новую характеристику для своего “Experience API”: эфемерность. Ох…

Пару месяцев назад я начал свой путь в поисках ответа на вопрос, почему все так сложилось и что с этим делать. Этот путь привел меня к сомнениям в прочнейшей из догм создания архитектуры приложений — MVC. И, когда я столкнулся с истинной силой реактивного и функционального программирования, я сосредоточился на простоте и уходе от раздувания, в создании которого наша отрасль так преуспела. Я думаю, вам могут быть интересны сделанные мной выводы.

Паттерн, который мы используем при разработке каждого экрана, MVC — Model-View-Controller. MVC был изобретен в те дни, когда Web еще не существовал и архитектура приложений была, в лучшем случае, толстым клиентом, который обращался напрямую к единственной базе данных по примитивной сети. И все же, спустя десятилетия, MVC до сих пор бессменно используется для создания Omni-Сhannel приложений.

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

Впервые я столкнулся с MVC в 1990-м году, когда NeXT выпустил Interface Builder (здорово осознавать, что это ПО до сих пор актуально в наши дни). В то время Interface Builder и MVC представлялись как большой шаг вперед. В конце 90-х паттерн MVC был приспособлен к работе через HTTP (помните Struts?) и теперь MVC это краеугольный камень в архитектуре любых приложений, на все случаи жизни.

Даже React.js, казалось бы, далеко ушедший от догмы MVC, использовал этот эвфемизм, когда они представляли свой фреймворк: “React это лишь View в MVC”.

В прошлом году, когда я начал использовать React, я чувствовал что это что-то совершенно иное — вы где-то изменяете часть данных и, в тот же момент, без явного взаимодействия View и Model, весь UI изменяется (не только значения в полях и таблицах). Из-за этого я так быстро разочаровался в модели разработки React, и, очевидно, я был не одинок. Я поделюсь мнением Андре Медейроса:
React разочаровал меня по нескольким причинам. В основном из-за плохо спроектированного API, которое вынуждает пользователя [..] смешивать в одном компоненте несколько concerns.

Как проектировщик серверного API, я пришел к выводу, что не существует хорошего способа добавить вызовы API во front-end реализованный на React, именно потому, что React сосредоточен только на View, и в его модели разработки вообще нет понятия Controller.

Facebook до настоящего момента сопротивляется исправлению этого пробела на уровне фреймворка. Команда React разработала паттерн Flux, который так же разочаровывает, и теперь Dan Abramov продвигает еще один паттерн, Redux, который движется, в целом, в правильном направлении, но — я продемонстрирую это далее — не обеспечивает надлежащего способа работы с API из front-end.

Вы можете подумать, что, разработав GWT, Android SDK и Angular, инженеры Google имеют четкое представление (на английском языке звучит как каламбур — strong view) о том, какова наилучшая архитектура для front-end. Но, если вы почитаете некоторые соображения об архитектуре Angular2, вы не обязательно испытаете теплое чувство, что, уж хотя бы в Google, люди понимают, что они делают:
Angular 1 не был построен на идее компонентов. Вместо этого мы прикрепляем контроллеры к различным [элементам] страницы по нашему усмотрению. Области видимости присоединялись или растекались (flow through) в зависимости от того, как наши директивы инкапсулируют сами себя (изолированные scope, кто-нибудь?)

Выглядит ли построенный на компонентах Angular2 намного проще? Не совсем. Основной пакет Angular2 сам по себе содержит 180 определений, а весь фреймворк доходит до ошеломляющие почти 500 определений, и это все поверх HTML5 и CSS3. У кого есть время изучить и совершенствоваться в такого рода фреймворке для построения web-приложений? Что будет, когда выйдет Angular3?

Поработав с React и увидев, что представляет из себя Angular2, я впал в уныние — эти фреймворки методично вынуждают меня использовать паттерн BFF Screen Scraping, в котором каждое серверное API подгоняется под набор данных, необходимых на экране, до мелочей.

Это был мой момент “Да пошло все к черту”. Я буду просто создавать приложения, без React, без Angular, без всяких MVC-фреймворков, и посмотрю, возможно мне удастся найти более удачный способ соединить View и нижележащее API.

Что мне действительно понравилось в React, так это взаимодействие между Model и View. То, что React не основан на шаблонах и что View сам по себе не может обратиться за данными, чувствовалось как разумное направление для изучения (вы можете только передать данные во View).

Достаточно поработав с React вы понимаете, что его единственное назначение — декомпозиция View на набор чистых (pure) функций и JSX-разметку:

<V params={M}/>

Это не что иное как:

V = f(M)

К примеру, web-сайт одного из проектов, над которым я сейчас работаю, Gliiph, построен при помощи функций подобных этой:


Рис.1 функция, отвечающая за генерацию HTML для компонента слайдера.

Эта функция наполняется из модели:


Рис. 2. Модель, работающая со слайдерами.

Когда вы понимаете, что обычные JavaScript-функции могут прекрасно выполнить нужную работу, вашим следующим вопросом будет: “зачем вообще я должен использовать React?”.

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

GraphQL? Вообще-то нет. Не обманывайтесь доводами, что если Facebook использует это повсеместно, то это подойдет вам. GraphQL это не более чем декларативный способ создания View Model. Когда вы вынуждены формировать Model в соответствии с View это проблема, а не решение. Как вообще команда React (в том смысле, что он reactive) может полагать, что это нормально, запрашивать данные при помощи Client Specified Queries:
GraphQL полностью управляется требованиями View и front-end разработчиками, которые их написали [...] С другой стороны, GraphQL-запрос возвращает в точности то, что запрошено клиентской стороной и не более

Что команда GraphQL, кажется, упускает — маленькое изменение, которое скрывается JSX-синтаксисом — что функции изолируют Model от View. В отличии от шаблонов или “запросов, написанных front-end разработчиками” функции не требуют, чтобы Model подходил ко View.

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

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

На данный момент, это не особо хорошая идея, напрямую встраивать подобные вычисления во view. Но совсем не трудно так же сделать view-Model чистой функцией, и, следовательно, нет необходимости использовать GraphQL когда вам нужен четко сформулированный ViewModel:

V = f( vm(M) )

Как ветеран MDE (Model-driven engineering — прим. переводчика), я могу заверить вас, что писать код бесконечно лучше, чем метаданные, будь то шаблон или сложный язык запросов наподобие GraphQL.

Подобный функциональный подход имеет несколько важных преимуществ. Во-первых, так же как React, он позволяет вам декомпозировать ваши View на компоненты. Естественный интерфейс, который они создают, позволяет вам реализовать темы для вашего web-приложения или web-сайта или выполнить рендеринг View при помощи других технологий (нативных, к примеру). Также, реализация при помощи функций может усовершенствовать способы реализации responsive-дизайна, которые мы используем.

Я не удивлюсь, к примеру, если в ближайшие несколько месяцев разработчики начнут создавать темы для HTML5 реализованные как компоненты на основе JavaScript-функций. На данный момент это то, как я делаю все мои web-сайты. Я беру шаблон и сразу же оборачиваю его JavaScript-функциями. Я больше не использую Wordpress — я могу получить лучшее от технологий HTML5 и CSS3 с тем же уровнем затрат (или меньшим).

Также, такой подход призывает к новому способу взаимодействия между дизайнерами и разработчиками. Кто угодно может написать подобные JavaScript-функции, тем более дизайнер шаблонов. Здесь нет “binding” синтаксиса, который необходимо изучать, нет JSX, нет шаблонов Angular, только простые JavaScript-функции.

Что интересно, с точки зрения reactive data flow, эти функции могут работать там, где это имеет больше смысла — на сервере или на клиенте.

Но, что наиболее важно, данный подход позволяет View декларировать минимальный контракт взаимодействия с Model и оставляет Model-у решать, каким образом лучше всего предоставит нужные данные View. Такие вопросы как caching, lazy loading, orchestration, consistency под полным контролем Model. В отличие от шаблонов или GraphQL, никогда не возникает необходимость делать прямой запрос, составленный с точки зрения View.

Теперь, когда у нас есть способ отвязать друг от друга Model и View, следующий вопрос в том, как создать полновесную модель приложения из этого? Как должен выглядеть “Controller”? Для ответа на этот вопрос, давайте вернемся к MVC.

Apple знает кое что об MVC, так как они “украли” этот паттерн из Xerox SPARC в начале 80-х и с того момента религиозно следуют ему:


Рис. 3. Паттерн MVC.

Ключевой вопрос здесь в том, и Андре Медейрос очень красноречиво объяснил это, что паттерн MVC “интерактивный” (в противоположность реактивному). В традиционном MVC Action (Controller) вызывает метод обновления у Model и в случае успеха (или ошибки) решает, каким образом обновить View. Как он (Андре Медейрос — прим. переводчика) замечает, Controller не обязан действовать именно таким образом. Есть другой, не менее разумный, reactive, способ, смысл которого заключается в том, что Action должен просто передавать значения в Model, независимо от результата, вместо того, чтобы принимать решение о том, как должен быть обновлен Model.

Ключевым вопросом тогда становится: “как вы интегрируете Action-ы в reactive flow?” Если вы хотите разбираться в том, что такое Action-ы, вам может захотеться взглянуть на TLA+. TLA расшифровывается как “Temporal Logic of Actions”, формулировка изобретенная доктором Лампортом, получившим премию Тьюринга за это. Согласно TLA+ Action-ы это чистые функции:

 data’ = A (data)

Мне очень нравится основная идея TLA+, поскольку она подтверждает тот факт, что функции это лишь преобразования заданного набора данных.

С учетом этого, реактивный MVC может выглядеть примерно как:

V = f( M.present( A(data) ) ) 

Данное выражение ставит условием что Action, когда он срабатывает, высчитывает набор данных из набора входных параметров (таких как пользовательский ввод), которые передаются в Model, который, в свою очередь, решает, когда и как себя обновить. Когда обновление выполнено, View рендерится из нового состояния Model. Реактивная петля замыкается. Способ, которым Model сохраняет и извлекает данные, не имеет отношения к reactive flow, и никогда, абсолютно никогда, не должен быть “написан front-end разработчиками”. Никаких оправданий.

Еще раз, Action это чистые функции, без состояния и без side effect-ов (из уважения к Model, исключаем logging, к примеру).

Реактивный паттерн MVC интересен, поскольку, за исключением Model (разумеется), все остальное это чистые функции. Говоря по совести, Redux реализует именно это паттерн, но с ненужными церемониями в отношении React и несколько избыточной связанностью между Model и Action-ами внутри reducer. Интерфейс между Action-ами и Model это лишь передача сообщений.

Исходя из вышесказанного, паттерн Reactive MVC, в том виде как он есть, не завершен. Он не масштабируется под реальные приложения как Дэн любит утверждать. Давайте рассмотрим простой пример, объясняющий почему.

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



Данное приложение имеет простую машину состояний:


Рис. 4. Машина состояний приложения для запуска ракет.

Как уменьшение (decrement), так и запуск (launch), являются “автоматическими” действиями. То есть каждый раз, когда мы вносим (или вносим повторно) состояние отсчета, условия перехода будут оценены и, если состояние счетчика больше нуля, будет выполняться действие decrement. А когда значение достигнет нуля — будет вызвано действие launch. Действие отмена (abort) может быть вызвано в любой момент, что приведет систему в отмененное состояние.

В MVC такая логика будет реализована в Controller, и, вероятно, вызываться по таймеру из View.

Этот параграф очень важен, поэтому прочтите его внимательно. Мы увидели, что в TLA+ Action не имеет side effects-ов и результирующее состояние вычисляется как только Model обрабатывает результат вызова Action и обновляет сам себя. Это существенное отступление от традиционной семантики машин состояний, где Action определяет результирующее состояние. Иными словами, результирующее состояние не зависит от Модели. В TLA+, Action-ы, которые разрешены, и, следовательно, доступны для вызова в state representation (то есть во View) не связаны напрямую с Action-ами, которые вызывают изменения состояния. Другими словами, машины состояния не должны быть кортежами, соединяющими два состояния (S1, A, S2) каковыми они обычно являются. Они являются, скорее, кортежами форм (Sk, Ak1, Ak2,...) которые определяют все разрешенные Actions для состояния Sk с результирующим состоянием вычисляемым после того, как Action был применен к системе и Model обработал изменения.

Семантика TLA+ предоставляет превосходный способ концептуализации системы, при котором вы представляете объект “состояние”, отделенный от Action и View (которые являются лишь отображением состояния).

Model в нашем приложении выглядит следующим образом:

model = {
counter:,
started:,
aborted:,
launched:
}

Четыре (управляющих) состояния системы ассоциированы со следующими значениями в Model:

ready = {counter: 10, started: false, aborted: false, launched: false }
counting = {counter: [0..10], started: true, aborted: false, launched: false }
launched = {counter: 0, started: true, aborted: false, launched: true}
aborted = {counter: [0..10], started: true, aborted: true, launched: false}


Model описывается свойствами системы и их возможными значениями, в то время как cостояние определяет Action-ы, доступные для заданного набора значений. Бизнес-логика подобного вида должна быть где-то реализована. Мы не можем ожидать, что пользователю можно доверить выбор, какие действия возможны, а какие нет. Это условие, которое невозможно обойти. Тем не менее, подобную бизнес-логику сложно писать, отлаживать и сопровождать, особенно когда вам недоступна семантика для ее описания, например, в MVC.

Давайте напишем немного кода для нашего примера с запуском ракеты. С точки зрения TLA+, next-action предикат логически определяется из отображаемого состояния. Как только текущее состояние представлено, следующим шагом будет выполнение next-action предиката, который вычисляет и выполняет следующий Action, если таковой имеется. Оно же, в свою очередь предоставит данные Model, который инициирует рендеринг нового state representation и так далее.


Рис. 5. Реализация приложения для запуска ракет.

Обратите внимание, что в случае клиент-серверной архитектуры нам придется использовать протокол наподобие WebSocket (или polling, если WebSocket недоступен) для корректного отображения состояния после того, как автоматический Action выполнится.

Я написал очень маленькую open source библиотеку на Java и JavaScript, которая строит состояние объекта с верной, с точки зрения TLA+, семантикой и привел примеры, которые используют WebSocket, Polling и Queueing для реализации клиент-серверного взаимодействия. Как вы видите по примеру с приложением для запуска ракет, вы не обязаны использовать эту библиотеку. Реализация состояния достаточно проста в написании когда вы понимаете, что нужно написать.

Я считаю, что теперь у нас есть все элементы для того, чтобы формально описать новый шаблон, как альтернативу MVC — SAM pattern (State-Action-Model), реактивный, функциональный паттерн, уходящий корнями к React.js и TLA+.

Паттерн SAM может быть описан в виде следующего выражения:

 V = S( vm( M.present( A(data) ) ), nap(M))

которое ставит условием, что View V системы может быть вычислено как чистая функция от Model, после применения Action-а A.

В SAM, A (Actions), vm (ViewModel), nap (next-action predicate) и S (state representation) являются и должны являться чистыми функциями. В случае SAM, то, что мы обычно называем “Состояние” (значения свойств системы) полностью ограничивают Model и логика, которая изменяет эти значения, недоступна за пределами Model.

В качестве примечания, предикат next-action, или nap() это callback, вызываемый как только state representation был создан и его предстоит отрендерить пользователю.


Рис. 6. Паттерн State-Action-Model (SAM).

Паттерн как таковой не зависит от каких-либо протоколов (и может быть с легкостью реализован через HTTP) и каких-либо клиентских/серверных технологий.

SAM не подразумевает, что вы всегда должны использовать семантику машины состояний дабы получить содержимое View. Когда вызов Action выполняется только из View, next-action предикат это null-функция. Хотя, это может быть хорошей привычкой — четко выделять управляющие состояния нижележащей машины состояний, поскольку View может выглядеть по разному в зависимости от того или иного (управляющего) состояния.

С другой стороны, если ваша машина состояний включает автоматические Action-ы, ни ваши Action-ы, ни ваш Model не будут чистыми без next-action предиката: либо некоторые Action-ы станут stateful, либо Model будет вызывать Action-ы, что не входит в ее задачи. Между прочим, и это неожиданно, объект состояния не содержит какое-либо “состояние”, это тоже чистая функция, которая рендерит View и вычисляет next-action предикат, делая и то и другое на основе значений свойств Model.

Ключевое преимущество это нового паттерна в том, что он четко отделяет CRUD операции от Action-ов. Model сам отвечает за свой persistence, который будет реализован при помощи CRUD операций, недоступных из View. В частности, View никогда не будет находиться в положении для “извлечения” данных. Единственное, что View может делать это запрашивать текущий state representation системы и инициировать reactive flow за счет вызова действий.

Action-ы представляют собой всего лишь канал для внесения изменений в Model. Они, сами по себе, не имеют side effect-а (для Model). Когда необходимо, Action-ы могут вызывать сторонние API (еще раз, без side effect для Model). Например, действие по изменению адреса может вызвать сервис для его проверки и передать в Model адрес, возвращенный данным сервисом.

Вот как действие “Смена Адреса”, вызывающее API для проверки адреса, будет реализовано:


Рис. 7. Реализация действия “Смена Адреса”

Составные элементы паттерна, Action-ы и Model-ы, могут быть составлены (composed) свободно:

Композиция на основе функций:

data’ = A(B(data))

Одноуровневая композиция (один и тот же набор данных передается двум моделям):

M1.present(data’)
M2.present(data’)

Композиция “Родительский-дочерний” (родительский Model управляет набором данных, передаваемым дочернему):

M1.present(data’,M2)
function present(data, child) {
        	// perform updates
        	…
        	// synch models
        	child.present(c(data))
}

Композиция посредством Publish/Subscribe:

M1.on(“topic”, present )
M2.on(“topic”, present )

Или

M1.on(“data”, present )
M2.on(“data”, present )


Для архитекторов, которые мыслят в терминах Систем Записей и Систем Участия (прочитать что это такое можно здесь — прим. переводчика) данный паттерн помогает определить интерфейс между двумя слоями (рис. 8) с Model, отвечающим за все взаимодействия с Системой Записей.


Рис. 8. Модель композиции SAM.

Паттерн composable сам по себе и вы можете реализовать экземпляр SAM запускающийся в браузере для поддержки поведения в стиле wizard-ов (к примеру, ToDo application), взаимодействующий с экземпляром SAM на сервере:


Рис. 9. Композиция экземпляров SAM.

Пожалуйста, обратите внимание, что внутренний экземпляр SAM представлен как часть state representation, сгенерированного внешним экземпляром SAM.
Восстановление сессии должно предшествовать срабатыванию Action-a (рис. 10). SAM делает возможной интересную композицию, когда View может вызывать сторонний Action, предоставляющее токен и callback, указывающий на Action системы, который авторизует и верифицирует вызов, перед тем как предоставлять данные для Model.


Рис. 10. Управление сессиями в SAM.

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

{ _name : ‘/^[a]$/i’ } // Names that start with A or a
{ _customerId: ‘123’ } // customer with id = 123


Model может выполнять необходимые операции для сопоставления с запросом, обновлять свое содержимое и инициировать рендеринг View. Схожий набор конвенций может быть использован для создания, обновления или удаления элементов Model. Есть целый ряд способов которые могут быть реализованы для передачи результата работы Action-ов в Model (data sets, events, actions). У каждого подхода есть свои достоинства и недостатки и, в конечном счете, подход может определяться исходя из предпочтений. Я предпочитаю подход с использованием data sets.

С точки зрения исключений (exceptions), так же как и в React, ожидается, что Model будет содержать информацию о соответствующем исключении как значения свойств (будь то исключение от Action или возвращенное CRUD-операцией). Данные значения будут использованы при рендере state representation чтобы отобразить исключение.

С точки зрения кэширования, SAM позволяет выполнять его на уровне state representation. Ожидаемо, кэширование результатов этих функций state representation должно привести к более высокому hit rate, раз мы теперь инициируем кэширование на уровне компонента/состояния вместо уровня Action/Response.

Реактивная и функциональная структура паттерна делает легким воспроизведение и unit-тестирование.

Паттерн SAM полностью меняет парадигму front-end архитектуры, поскольку, основываясь на TLA+, бизнес-логика может быть четко разделена на:
  • Action-ы как чистые функции
  • CRUD-операции в Model
  • Состояния, которые управляют автоматическими Action-ами

С моей точки зрения, как разработчика API, паттерн сдвигает ответственность за проектирование API обратно на сервер, с минимально возможным контрактом между View и Model.

Action-ы, как чистые функции, могут быть повторно использованы между моделями если Model принимает возвращаемый результат от Action. Мы можем ожидать, что библиотеки Action-ов, темы (state representation) и, возможно, библиотеки Model-ов будут расцветать, раз они теперь композируются независимо.

С SAM микросервисы естественным образом помещаются за Model. Фреймворки наподобие Hivepod.io могут быть подключены на этом уровне, по-большому счету, as-is.

Что наиболее важно, данный паттерн, подобно React, не требует какого-либо data binding или шаблонов.

Я ожидаю, что со временем SAM поспособствует тому, что virtual-dom будет повсеместно реализован в браузерах и новые state representation будут напрямую обрабатываться через соответствующее API.

Я нахожу данное исследование преобразующим: времена существовавшего десятилетия подхода Object Orientation похоже, почти прошли. Я не могу больше мыслить в терминах иных чем реактивный или функциональный. Те вещи, которые я реализовал с помощью SAM и скорость, с которой я могу их реализовать, беспрецендентны. И еще один момент. Я теперь могу сосредоточиться на проектировании API и сервисов, которые не следуют паттерну “screen scrapping”.

Я хочу поблагодарить и выразить признательность людям, которые любезно согласились проверить эту статью: Prof. Jean Bezivin, Prof. Joëlle Coutaz, Braulio Diez, Adron Hall, Edwin Khodabackchian, Guillaume Laforge, Pedro Molina, Arnon Rotem-Gal-Oz.

Об авторе:
Жан-Жак Дюбре является основателем xgen.io и gliiph. Он занимается созданием Service Oriented Architectures и платформ API последние 15 лет. Является бывшим членом исследовательской команды в HRL и получил докторскую степень в University of Provence (Luminy campus) (ныне Aix-Marseille University — прим. переводчика), доме языка Prolog. Изобретатель методологии BOLT.
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 254
  • +12
    После первого листинга кода читать дальше как-то не захотелось. Но чувак звучал очень умно, и я попытался дать ему шанс. Но после формулы data’ = A (data) я все-таки сдался. Он ведь пытается сделать очередный основанный на FRP подход/фреймворк, так?
    • +8
      Пытается быть «не такой как все».
      • +2
        Мне нравятся решения фейсбука уже за то, что они придумывают им новые названия, а не выбирают «MVC», или другой избитый TLA (https://en.wikipedia.org/wiki/Three-letter_acronym).

        TLA+ от майкрософт это гениально :)
      • +7
        По поводу
        чувак звучал очень умно

        Он ученый и ему свойственна академичность. Возможно и несколько избыточная.

        По поводу
        пытается сделать очередный основанный на FRP подход/фреймворк

        Лично мне статья показалась скорее попыткой под другим углом взглянуть на MVC.
        Да, приведенный код весьма далек от идеала. Но те проблемы, которые он обозначил в начале статьи, от этого ведь никуда не пропадают. Как направление для развития своих мыслей при проектировании, имхо, статья стоит внимания. Как конечное непререкаемое решение — тут соглашусь с вами, не стоит.
        • +13
          Я, честно говоря, не очень понял, в чем у него проблема, но, кажется, дело вот в чем:
          Dev — итак, на этом экране нужны данные x, y и z. Не мог бы ты сделать API, которое вернет данные в формате {x:, y:, z: }

          Потом, видимо, к нему приходит Dev2 и просит {x, y, w}. И он, вместо того чтобы сделать 4 отдельных API и сказать Dev и Dev2, что нужные данные доступны по таким-то REST-эндпойнтам, делает ему новое отдельное API, и они, естественно, растут в геометрической прогрессии, и автор фрустрирует.

          Ну так проблема не в MVC, не в React.js и не в 500 сущностях в Angular2.
          • +3
            Если раскидать данные по 4-м отдельным REST-эндпойнтам, то это не сильно помогает, если на front-end нужен целый граф объектов. Да, будет меньше эндпоинтов, но будет больше запросов за данными, чтобы собрать нужный граф объектов на клиенте.
            Дополнительные проблемы возникают от того, что на front end зависимости объектов друг от друга со временем рискуют стать неуправляемыми.
            Вообще про это гораздо лучше меня рассказывал Samer Buna в своем выступлении.
            Ну и как раз из-за этих проблем Facebook изначально и разработал React, Flux и GraphQL, поскольку MVC не масштабируется под их потребности
            Автору же решение Facebook в виде React и GraphQL кажется недоработанным, о чем он и говорит в статье.
            • +3
              Просто FB не умеет в MVC, судя по этой картинке:
              wtf?
              image

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

              В итоге они взяли всё тот же MVC (одну из вариаций), только назвали его по-другому.

              Может я что-то не понимаю, но в чём отличие от флакса от классического MVC? Там модель содержит всю логику и оповещает подписавшиеся на неё вьюхи о изменениях в своих данных. Там почти такой же однонаправленный поток данных: от модели ко вьюхам. Никакого двунаправленного связывания, от которого многие не в восторге. А действия пользователя всё так же идут от вью через контроллеры к моделям (или другим контроллерам).

              Да и автор, по-сути, опять пересказал старый добрый MVC.

              Ну и второй коммент в этой статье доставляет)
              • +1
                Ну и не забываем, что это все от парней, которые не осилили написать френд-ленту и сказали, что HTML5 сосет по производительности.
                • +7
                  Ещё вспомнился давний пост на хабре, где FB превозмогал ограничения андроида на количество классов. Вот только они в своё приложение хотели запихнуть почти всё (помню оно ужасно тормозило и выжирало кучу памяти, так что я его снёс и больше не трогал). При этом, приложение ВК с почти аналогичным функционалом работало отлично.
                  Опять сами создали себе проблему и опять героически боролись с ней. Ну хоть делом заняты…
                • +4
                  По поводу мотивации Facebook в создании React/Flux/GraphQL согласен — мотивация о немасштабируемости MVC мне кажется слабоватой, чтобы переходить на GraphQL, с которым вопросов куда больше чем ответов.

                  По поводу данной конкретной статьи — в том и прелесть идеи автора, что без существенного слома можно получить плюшки.

                  Возможно тут дело просто в личной боли каждого. У меня за душой есть здоровенный пожилой проект, который еще не смертельно, но уже страдает от сложности. Изучая, к примеру, грядущие Angular 2 и Aurelia, я не смог увидеть, в чем они мороку с этим приложением помогут уменьшить. А пока читал оригинал этой статьи — несколько решений в голову сами собой пришли. И это без всяких переходов на новые фреймворки.
                • +4
                  Почитайте стандарт jsonapi.org, они в принципе весьма красиво решили проблемы «графов» и связанных ресурсов.

                  По поводу FLUX, что вы ответите на такое: FLUX это ничто иное как православный MVC (79-ого года, когда контроллер ничего не знал о view толком) + Event Sourcing? А так как Event Sourcing это деталь реализации модели — то это просто старый добрый MVC. И мотивация в введении понятия Flux как по мне — устранение разночтений. Интерпритаций и видоизменений MVC уже как грязи, и большая часть похоже на вот эту картинку «неправильного MVC». Потому лучше просто описать «правильное» и переименовать. И вот эту мысль я поддерживаю. А введение event sourcing — это более чем логично когда отслеживание состояния приложения это проблема. Нет состояния — нет проблем.
                  • +1
                    что вы ответите на такое: FLUX это ничто иное как православный MVC

                    Плюсану вам карму и потопаю перечитывать про FLUX и историю развития MVC.
                • +5
                  Я больше не использую Wordpress — я могу получить лучшее от технологий HTML5 и CSS3 с тем же уровнем затрат (или меньшим).

                  На этом месте словил очередной WTF.
                  Неоднозначные ощущения от прочтения, вроде бы кое-где и мысли правильные озвучиваются и в то же самое время множество моментов с которыми если нельзя согласиться, то вообще непонятно зачем.
                • +4
                  Лично мне статья показалась скорее попыткой под другим углом взглянуть на MVC.


                  Рекомендую еще немного повернуть угл и посмотреть на развитие идей вокруг MVC на протяжении времени. Вообще разбираться в чем-то проще, если знать как шла мысль.
                • +2
                  <perfection_mode>Даже оригинал применил не-букву ударения ( ’, &#180;, не апостроф), поэтому формально замечание верно, в JS не работает, а с таким символом не было бы формального замечания:
                  data̕ = 5;  // символ "запятая сверху справа" - &#789;
                  

                  И тогда в JS это работает как переменная.
                  Вообще, штрих — это из численных методов математики, там это обозначает следующую итерацию переменной. Ну а JS не обязан соблюдать синтаксис формул численных методов, хотя может, если поискать букву-штрих среди юникодов.
                  </perfection_mode>
                • –6
                  Транслейшен с English ужасен. Тут какбе следует understand, что те, кто знает English, лучше прочитают статью на English; а те, кто не знает, будут посылать икательные рефлексы переводчику, every time натыкаясь на English вордс.
                  • +4
                    Да. Согласен, что далеко не идеал )
                    Изначально пытался писать как можно больше на русском, но мешанина из «действий», «представлений» и «представлений состояний» получалась такой, что я сам с трудом читал написанное.
                    Если писать «экшен», «вью» и т.д., то те, кто не знает английский вряд ли будут сильно счастливее.
                    Поэтому решил все-таки устоявшиеся термины писать на английском.
                    • +13
                      А мне понравился перевод, ничего не смутило.
                      • +1
                        почему тогда в тексте остались conserns, side effects?
                        • +2
                          Сoncern при переводе на русский язык плохо отделяется от responsibility. Мне неизвестны устоявшиеся русские переводы этих слов в среде разработчиков, которые бы четко отделяли одно от другого. То же самое касается, к примеру, cohesion и coupling.

                          По поводу side effect — никогда не слышал чтобы разработчики употребляли термин «побочный эффект» или «сторонний эффект».

                          Ну и оба термина часто используются разработчиками в речи без перевода.
                          • +1
                            к примеру, cohesion и coupling.

                            Зацепление и связанность, или внутренняя и внешнаяя связаность, если уж по смыслу подбирать. В нашем случае можно было бы перевести concerns как область ответственности, а responsibility — ответственность. Мы же не гугл транслейт, нам главное просто смысл передать а не дословно переводить.

                            разработчики употребляли термин «побочный эффект» или «сторонний эффект».

                            У вас выборка не репрезентативна. Эти слова очень даже хорошо переводятся и при этом смысл не теряется.
                            • +1
                              Разработчики говорят сайд-эффекты, но все поймут если написать «побочный эффект». Вам же не приходит в голову писать дошь (или dosh :), вместо дождь, из за того что большинство так говорит в разговорной речи.
                      • +8
                        Расскажите автору про Object.assign() или какой-нибудь другой способ слияния объектов. А то трудно серьёзно воспринимать человека, который ещё пишет как на рисунке 1.
                        • +5
                          Кстати, функция, которую написал автор, еще и не является чистой…
                          • +1
                            Почему?
                            • +2
                              var s = { };
                              theme.slider(s);
                              console.log(s); // wat?!
                              
                              • +2
                                ¯ \ _ (ツ) _ / ¯
                                Я не знаю джаваскрипта, предполагал что там аргументы по значению передаются.
                                • +2
                                  Так и есть, они передаются по значению. Но они — объекты. Внутренности объекта не копируются, а разделяются между разными ссылками на него.
                                  • +3
                                    что переводится как «объекты передаются по ссылке всегда».
                                    • –2
                                      Нет, не по ссылке:

                                      function foo (x) {
                                        x = { a: 10 };
                                      }
                                      
                                      var s = { a: 5 };
                                      foo(s);
                                      console.log(s.a); // 5
                                      


                                      Если бы объекты передавались по ссылке — в консоли бы оказалось 10.
                                      • +9
                                        Объекты передаются как раз по ссылке. А вот ссылка на объект передается по значению.
                                        • –1
                                          Эм, ссылка это просто «имя» объекта у нее нет «значения».

                                          Т.е. семантически «x = { a: 10 };» читается как объекту {a: 10} присвоили имя «x».
                                          Соответсвенно, то что какому-то объекту присвоили имя «x» никак не может повлияет на содержание объекта с именем «s».

                                          Технически же да, ссылка обычно реализуется через указатель который хранит в какой-то форме адрес объекта.
                                          • +2
                                            Переменная в динамическом языке является не лексической ссылкой (нет отдельного этапа компиляции, преобразовывающей имена в адреса, это происходит в рантайме), а именно переменной, которая занимает определённое количество памяти и хранит в себе значение (даже если это значение — undefined). Присвоение объекта переменной в JS — это рантаймная операция, в переменную попадёт ссылка на присвоенный объект, а не просто «возникнет имя у объекта». Вероятно, вы говорили о C++, а не JS.
                                            • +2
                                              A Reference is a resolved name or property binding. A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Symbol, a Number, or an Environment Record (8.1.1). A base value of undefined indicates that the Reference could not be resolved to a binding. The referenced name is a String or Symbol value.

                                              www.ecma-international.org/ecma-262/6.0/#sec-reference-specification-type
                                            • +2
                                              Я тут имел в виду не только JS. В некоторых языках можно ссылку передавать по ссылке, например в C# с использованием ref.
                                              • +1
                                                Эм, там же кстати и написано:

                                                Не следует путать понятие передачи по ссылке с понятием ссылочных типов.Эти два понятия не совпадают.Параметр метода может быть изменен с помощью ref независимо от того, принадлежит ли он к типу значения или ссылочному типу.При передаче по ссылке упаковка-преобразование типа значения не производится.
                                          • +2
                                            Да, ваш пример показывает огромную пропасть в понимании определения «ссылка».
                                            • +2
                                              Скорее все обсуждение показывает огромную пропасть в понимании определения «передача аргумента по ссылке» :)
                                            • +1
                                              Но ведь вы в функции делаете переприсвоение x = { a: 10 }, следовательно создается новая ссылка. Если же делать так:
                                              function foo (x) {
                                                x.a = 10;
                                              }
                                              

                                              то в s.a будет 10
                                              • +1
                                                Именно так.

                                                В случае с объектами мы в переменной в качестве значения имеем не объект а ссылку на него. А ссылки уже передаются по значению, то есть происходит копирование ссылки, увеличивается значения счетчика ссылок у объекта и т.д. Все как в других языках программирования.
                                              • –1
                                                function foo(x) {
                                                  x.a = 10
                                                }
                                                
                                                var s = { a: 5 }
                                                foo(s)
                                                console.log(s.a)
                                                


                                                Днище
                                          • 0
                                            Вот развёрнутый ответ на ваш вопрос http://dmitrysoshnikov.com/ecmascript/ru-chapter-8-evaluation-strategy/ — Тонкости ECMA-262-3. Часть 8. Стратегия передачи параметров в функцию.
                                      • +1
                                        <perfection_mode>Опять же, формально придирка верная, и обычно здесь пишут var arg1 = obj.arg1 ||"arg1_default";

                                        Но почему написано так — очевидно, эта конструкция подменяет es6-конструкцию «default args» и аргумент у вызова этой функции — всегда натуральный именованный список, не переменная (т.е. типа f({arg1:'val1', arg2:'val2'}).

                                        Если вообще отойти от es5 и удариться в es6-запись (потому что альтернатива Object.assign() — из той же оперы), то определяли бы функкцию в виде
                                         function f(obj = {arg1:'default_val1', arg2:'default_val2'}){...}
                                        

                                        а если считать, что необходимость именованного списка в es3-5 была как раз вызвана отсутствием значений аргументов по умолчанию — то вообще
                                         function f(arg1='default_val1', arg2='default_val2', ...){...}
                                        
                                        </perfection_mode>
                                    • +12
                                      Всю статью можно ужать в одно предложение: «Наши девелоперы за… ли меня, и я решил, что так больше продолжаться не может».

                                      Как уже было сказано ранее, проблемы в MVC нет, есть проблема в девелоперах.
                                      • –1
                                        Как уже писал выше, если бы не было проблем с MVC, то Facebook не изобретал бы Flux и GraphQL.
                                        Речь же не о том, что MVC безоговорочно фигня и все — не используем. До какого-то предела сложности он отлично работает. Начиная с какого-то предела уже приходится искать альтернативы. Каждому гвоздю свой молоток.
                                        • +5
                                          У фейсбука проблемы с MVC, потому что они делают с ним что-то не то, судя по иллюстрациям в этой статье.
                                          • +3
                                            Предлагаю гипотезу. У Фейсбука осложнения потому, что их главные архитекторы очень сильно подсели на PHP (или на страх о переходе с него на что-то другое). Они даже компилятор PHP написали, боясь потерять свой код. И реактивная концепция, согласно этой гипотезе, к ним пришла не из FP, а именно из PHP. Чистая функция render в JSX — это квинтэссенция подхода PHP («отрисовать страницу и умереть, не заботясь о менеджменте ресурсов»), а не принцип из FP. И именно потому, как мне думается, Redux (который кажется естественным продолжением «Реакта как FP-концепции») родился не в Фейсбуке.
                                            • +2
                                              Предлагаю вам другую гипотизу. Проекты масштабов Facebook могут позволить себе любое безумие, лишь бы быть экономически эффективными и продолжать быстро впиливать новые фичи. Помимо PHP, HHVM (между прочим они почти все на Hack пишут, что несколько не то же самое). И имея то же количество кода, что у ребят из фэйсбука, я думаю вы бы тоже передумали «переписывать» что-то.

                                              Чистая функция render в JSX — это квинтэссенция подхода PHP («отрисовать страницу и умереть, не заботясь о менеджменте ресурсов»)


                                              Ммм, а в чем принципиальная разница? Чистая функция, извините, остается жить после того как вернула результат?
                                              • +1
                                                > вы бы тоже передумали «переписывать» что-то

                                                Я же не говорил, что они поступают нецелесообразно. Страх перед уходом от PHP вполне может быть следствием финансового планирования. Или вы решили, что я PHP не люблю?

                                                > а в чем принципиальная разница?

                                                Никакой принципиальной разницы. В этом, вроде бы, мой тезис и заключался.
                                        • +4
                                          тяжело сказать, хорошую ли идею двигает автор, так как с первого прочтения въехал не до конца, буду перечитывать.

                                          но по тому, во что въехал
                                          1) главный императив — борьба со сложностью, пока мне кажется решение излишне сложным
                                          2) ок, что делать с сайд эффектами? во фронтенде их уйма, да и к тому же многое асинхронно
                                          • +3
                                            Нет никаких «состояний».
                                            Как только кто-то говорит «статус», моя рука тянется к пистолету. Любой статус — это просто функция от полной истории!
                                            • +2
                                              Почему было бы просто и понятно не изложить в чем суть проблемы? Девелоперы приходят — это конечно серьезная проблема, но не очень внятная. Отсюда и решение проблемы такое же не внятное. Хотя, если честно, то объяснения в статье я тоже понял не до конца. Кто-нибудь может сказать в двух словах, чем то, что описывает автор, отличается от mvc?
                                              • +1
                                                Когда уже допилят webasm и весь это зоопарк отправится в компостную яму?
                                                • +5
                                                  Awww, вы думаете, что с приходом WASM количество говнякеров во фронтенде уменьшиться? Раньше порог входа в JS (небольшой, но все же) хоть как-то ограничивал число желающих, а теперь фронтенд-фреймворки будут писать еще и на всем остальном.
                                                  • +2
                                                    с приходом webasm зоопарк только быстрее начнет размножаться. И это хорошо, потому как пока мы не достигли того уровня энтропии, когда разработчики наконец перестаную велосипеды делать и начнут наконец делом заниматься.

                                                    в целом же webasm позволит нам просто портировать узкие части системы написанные на языках типа Си для выполнения оных в браузере. Самый пожалуй показательный пример — pdf.js, или же многочисленные библиотеки для работы с DSP и т.д.
                                                  • +6
                                                    Не дочитал статью, остановился на том, как вместа синтаксиса вьюхи он придумал оборачивать вьюху в «чистый js» или что-то вроде. Ну да неважно, я другого понять не могу:

                                                    Он пишет, что проблема в том, что каждый хмырь требует от сервера отдавать данные в своем формате. Одному так, другому эдак, третьему по-другому поле назови в ответном json'e. Ну так надо собрать всех разрабов — фронта и мобил — и договориться, в каком виде отдавать данные. А кто после начинает придумывать «а давай тут поле переименуем» — по рукам давать.

                                                    Проблема решена. При чем тут mvc? Куда-то его непонятно куда унесло в своих рассуждениях, стоит дочитывать статью?
                                                    • +5
                                                      Те же мысли вышли и у меня. Автор пытался «решить» проблему взаимодействия членов команды (по другому это не назвать) обвиняя инструменты, которые используют разработчики. Вот как-то у меня не возникает таких проблем, и я обычно пишу апишки не привязываясь к UI. Если кто-то начинает говорить мол «так удобнее одним запросом выбрать», я объясняю что лучше сделать 3 паралельных запроса. Однако у других разработчиков встречал такое — просто не могут отстаивать свое мнение, что по итогу приводит к очень странным апишкам, потому что ее проектирует фронтэндер исходя именно из UI.

                                                      Пишу бэкэнд для фронтэндщиков и мобильщиков 5 лет, требую апишки от бэкэндщиков 3 года.

                                                      p.s. Поясню на всякий случай почему сделать 3 паралельных запроса эффективнее чем сделать один. Дело в том, что в linux системах (а наши сервера на нем крутятся), имеют буфер отправленных пакетов на каждое соединение. То есть мы можем послать до 10-ти пакетов (зависит от настроек ядра, но по умолчанию 10) не дожидаясь подтверждения о доставке. Как правило размер пакета ограничен MTU, который обычно равен 1500 байт. Итого, не дожидаясь подтверждения от клиента мы можем отправить аж 15Кб. Если контент ответа с сервера не умещается в 15 килобайт, то тогда следующую пачку данных мы сможем отправить только когда нам придет подтверждение о доставке наших пакетов. А это, в зависимости от лэтенси, довольно много времени.

                                                      Итого, 3 ответа по 15 килобайт, придут, в большинстве случаев, быстрее, чем 1 ответ в 45 килобайт.
                                                      • +3
                                                        Это все работает пока у нас не больше 3х клиентов, одновременно беспокоящих сервер :)

                                                        Но для ненагруженных серверов так и есть, причем не только в Линуксе.
                                                        • +3
                                                          Это все работает пока у нас не больше 3х клиентов, одновременно беспокоящих сервер :)

                                                          кеширование, горизонтальное масштабирование, балансировка нагрузки… В этом же прелесть REST-like апишек, их легко масштабировать.
                                                        • 0
                                                          Всё зависит от того, как принимаются решения. Если есть архитектор, то решения принимаются быстро, хоть и не всегда оптимально. Если у вас скрамный скрам с "равноправием", то обсуждение какой-нибудь мелочи может затянуться на несколько дней, что контрпродуктивно.

                                                          История из жизни. На одном проекте, где я разрабатывал апи, было очень сложно убедить мобильщиков чуть поднапрячься и реализовать полноценные модели, абстрагирующие от протокола. Ведь это так просто — дёрнуть по ресту ручку и получить все необходимые данные в виде готовых вложенных структур. Используемый ими фреймворк очень поощрял такой подход. Аргументы про экспоненциальный рост дублирования в выдаче, затирание изменений при конкурентной записи, отметались простыми "никто так не делает" и "зачем ты так всё усложняешь?". В итоге, мне всё же удалось отстоять несколько ключевых моментов (плоская выдача вместо иерархической, апдейты диффами), но не потому, что убедил, а потому что они сдались и затаили обиду. Было бы у меня чуть меньше авторитета, пришлось бы сдаваться уже мне, и пилить "традиционный апи" с традиционными же детскими болезнями, а потом через год всё равно перепиливать.
                                                          • 0
                                                            Если есть архитектор, то решения принимаются быстро

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

                                                            Если у вас скрамный скрам с «равноправием»

                                                            Скрамный скрам, канбаны и прочая "гибкая" фигня говорит нам лишь о том, что команда сама себя сорганизует. То есть то что демократия не отменяет того, что у кого-то авторитетность мнения будет выше, и кто-то станет на проекте лидом. И если этот лид адекватный, все будет весьма и весьма продуктивно. Опять же в случае спорных моментов, просто можно сдлеать оценку двух вариантов, оценить риски и вперед эксперементировать с наиболее оптимальным вариантом.

                                                            плоская выдача вместо иерархической, апдейты диффами

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

                                                            Мне не очень нравится динамическая структура ответов (например как в jsonapi) ибо вызывает проблемы. в частности на android, но...

                                                            что до патчей… справедливости ради, это сильно усложняет реализацию на клиенте, во всяком случае сказывается отсутствие адекватных решений (хотя не надо верить мне наслово, ресерчил где-то года полтора назад, надо будет повторить). В подавляющем большинстве случаев хватает PUT + ETag и т.д. В моей практике было не так много проектов, где приходилось быть писсемистом, и лепить полноценные diff-ы. В одном из таких случаев мы и на бэкэнде использовали event sourcing, невилировать вероятность потери данных.

                                                            Оно конечно правильнее так, через Patch с коллекцией действий, или хотя бы diff ресурса, но подобное усложнение надо применять исходя из требований к проекту.
                                                            • 0
                                                              Все люди разные, с разным опытом, разным взглядом на идеальную архитектуру. Договориться порой очень сложно. И приведение казалось бы железных аргументов не всегда помогает. Всё скатывается в голосование большинством. Да, можно избрать архитектора, но и архитектором станет лишь тот, кто не противоречит мнению большинства. А будешь сильно возражать — большинство скажет, что не хочет с тобой работать, так как "ты всё время споришь, тормозя работу". Демократия не помогает достичь лучшего решения, только посредственного. Поэтому и нужен человек с опытом и рациональным мышлением для принятия окончательного решения. Как найти/выявить такого человека — вопрос отдельный. Но точно не голосованием.

                                                              Касательно плоской выдачи, приведу пример из жизни. На одном проекте было дерево тэгов и выводилось оно наиболее естественным способом — json со вложенными структурами. Всё было хорошо, пока клиенты не стали жаловаться, что у них всё тормозит. Стали разбираться. Оказалось, что у некоторых клиентов при сравнительно небольших деревьях, выдача получалась просто огромной. А всё потому, что один тэг мог быть сразу в двух других и соответствующие поддеревья выдавались в двух местах. Таким образом, у активно пользующихся тэгами клиентов, выдача дерева тэгов росла экспоненциально. Чтобы сформировать эту выдачу серверу приходилось тратить кучу времени и памяти, чтобы транслировать плоский список моделей в json-дерево. Да и на клиенте работать с голым json не особо удобно, поэтому первое, что делал клиент — рекурсивно пробегался по json и собирал дерево объектов с перекрёстными ссылками. Отказ от иерархической выдачи позволил заменить на клиенте рекурсивный обход итерированием по массиву; на сервере позволил заменить сложный алгоритм формирования дерева тривиальной выдачей списка; ну и в целом всё стало работать куда быстрее и кушать меньше трафика.

                                                              Диффы разные бывают. В частности, мы остановились на диффах такого плана:

                                                              {
                                                                  "next" : { favorite : [ "mary" , "alice" ]  },
                                                                  "prev" : { favorite : [ "alice" ] }
                                                              }

                                                              Это означает, что в список favorite перед "alice" необходимо добавить "mary". Но можно и полностью затирать свойство, просто не указывая его в "prev":

                                                              {
                                                                  "next" : { favorite : [ "mary" , "alice" ]  },
                                                                  "prev" : {}
                                                              }

                                                              Фактически мы пол дня спорили о том, чтобы мобильщики писали { "next" : {...} } вместо {...}, чтобы потом можно было легко прикрутить диффы, не ломая апи. :-D
                                                              • –1
                                                                Ещё есть классный паттерн — uri в качестве идентификатора. Но его отстоять тогда не удалось, к сожалению.

                                                                // GET /search=MVC/proj=habr?fetch=name,found(name,tag,prof(name))
                                                                {
                                                                    "search=MVC/proj=habr" : {
                                                                        "name" : "MVC",
                                                                        "found" : [ "post=277113" , "user=lair" ]
                                                                    },
                                                                    "post=277113" : {
                                                                        "name" : "Почему я больше не использую MVC-фреймворки",
                                                                        "tag" : [ "tag=web-разработка", "tag=angularjs", "tag=javascript", "tag=patterns" ]
                                                                    },
                                                                    "user=lair" : {
                                                                        "name" : "Сергей Роговцев",
                                                                        "prof" : [ "prof=architect" ]
                                                                    }
                                                                    "prof=architect" : {
                                                                        "name" : "Архитектор"
                                                                    }
                                                                }
                                                                • +1
                                                                  uri в качестве идентификатора

                                                                  URI — Uniform Resource Identifier. Ну то есть это как бы, даже не паттерн, это просто использование URI по назначению.

                                                                  Есть такая штука как HATEOAS, то что вы привели выше сильно смахивает на эту штуку, но в слегка извращенной форме.
                                                                  • 0
                                                                    Именно так. Тогда получается гибкий и самоописывающийся формат. А не так, что нужно взять идентификатор, сформировать из него URL по шаблону и только тогда можно запросить ресурс.

                                                                    HATEOAS — несколько неудобная реализация той же идеи.
                                                                    • 0
                                                                      в слегка извращенной форме

                                                                      Я бы сказал даже не слегка.
                                                                      • 0
                                                                        Не развернёте свою мысль?
                                                                        • 0
                                                                          В приведенном примере нет ни настоящих адресов, ни настоящих ссылок (если есть, то они неопознаваемы). Даже если оставить за скобками собственно состояние, ради которого HATEOAS придумывается, как пользоваться этими недоссылками, если это вообще ссылки — непонятно. Честный же гиперлинкинг предполагает в минимальной своей форме — ссылку, которую можно использовать напрямую, просто вставив ее в запрос, на следующем этапе появляются отношения, описываемые ссылками (и как раз в этот момент начинается самое интересное), на следующем этапе все это описывается формально (к сожалению, ни одного реального стандарта до сих пор нет, есть k конкурирующих).

                                                                          (Ну и да, URI — это идентификатор ресурса в сети, использовать его внутри документа — отдельное неочевидное развлечение)
                                                                          • 0
                                                                            В данном случае используется relative-uri (да, слэша вначале идентификаторов не хватает), чтобы не раздувать объём выдачи и не хардкодить конкретное имя сервера.

                                                                            Это URL — идентификатор ресурса в сети, а URI — абстрактный идентификатор, который не всегда имеет к сети отношение.
                                                                            • 0
                                                                              В данном случае используется relative-uri

                                                                              Угу. Осталось понять, как об этом догадаться, и какая у него семантика.
                                                                              • 0
                                                                                Именно. Читать не хотят, а сами догадаться не могут. Вот и получается, что кругозор вырождается в точку зрения.
                                                                                https://tools.ietf.org/html/rfc3986#section-4.2
                                                                                • 0
                                                                                  А толку-то с этого RFC? Он ничего о семантике и положении URI внутри вашего документа не скажет.
                                                                                  • 0
                                                                                    Что вы понимаете под "семантикой" и "положением URI внутри документа"?
                                                                                    • 0
                                                                                      Под семантикой я понимаю, как ни странно, семантику — то, какой смысл вкладывается в элемент. В приведенном вами примере есть набор строковых ключей, за каждым из которых лежит какой-то объект. Все эти ключи — равноправны, и понять, чем они друг от друга отличаются, из самого документа — нельзя.

                                                                                      Далее, если посмотреть на пример прищурившись, то можно увидеть, что часть этих ключей используется в других местах (например, в блоке found), и наверное имеется в виду, что это — внутренние связи через идентификаторы. (Но есть и аналогичные по виду ключи — теги — которые нигде в документе больше не используются. А что с ними делать?) Но машина-то не умеет смотреть прищурившись, ей все надо объяснять. И поэтому, чтобы работать с вашим форматом, нужен специально написанный прикладной API, который будет знать, что в блоке found лежат идентификаторы, по которым надо забрать блоки с уровня выше, а потом в каких-то из них повторить аналогичные процедуры.

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

                                                                                      Сравните с тем, как устроен, например, HAL.
                                                                                      • 0
                                                                                        Под семантикой я понимаю, как ни странно, семантику — то, какой смысл вкладывается в элемент. В приведенном вами примере есть набор строковых ключей, за каждым из которых лежит какой-то объект. Все эти ключи — равноправны, и понять, чем они друг от друга отличаются, из самого документа — нельзя.

                                                                                        А как понять, что "status": "shipped" — это одно из значений перечисления [ "shipped", "processing", "cancelled" ], а не произвольная строка? Для передачи семантики, необходима расширенная система типов (именованное перечисление, значение в заданной валюте, ссылка на сущность определённого типа и тд). В то же время JSON имеет ограниченную систему типов (строка, число, словарь, список, флаг, пустота). HAL же вводит лишь один единственный дополнительный тип "список ссылок на другие сущности". То есть не решает проблему систематично, а не понятно зачем вставляет частичный костыль. В результате, вместо того, чтобы писать просто:

                                                                                        {
                                                                                            "status" : "processing",
                                                                                            "items": [ "box=123" , "letter=321" ]
                                                                                        }

                                                                                        Или ещё проще и гибче:

                                                                                        status processing
                                                                                        items
                                                                                            box=123
                                                                                            letter=321

                                                                                        Приходится писать сложно:

                                                                                        {
                                                                                            "_links": {
                                                                                              "items": [{
                                                                                                  "href": "box=123"
                                                                                              },{
                                                                                                  "href": "letter=321"
                                                                                              }]
                                                                                            }
                                                                                            "status" : "processing",
                                                                                        }

                                                                                        И всё равно где-то в коде держать логику по преобразованию status из строкового типа в элемент перечисления и обратно.

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

                                                                                        Их свойства не зафетчили — вот их и нет в выдаче.Давайте зафетчим и их:

                                                                                        // GET /search=MVC.proj=habr.type=post?fetch=name,found(name,tag(name,rating))
                                                                                        {
                                                                                            "search=MVC.proj=habr.type=post" : {
                                                                                                "name" : "MVC",
                                                                                                "found" : [ "post=277113" ]
                                                                                            },
                                                                                            "post=277113" : {
                                                                                                "name" : "Почему я больше не использую MVC-фреймворки",
                                                                                                "tag" : [ "tag=javascript", "tag=patterns" ]
                                                                                            },
                                                                                            "tag=javascript" : {
                                                                                                "name" : "javascript",
                                                                                                "rating" : 4
                                                                                            },
                                                                                            "tag=patterns" : {
                                                                                                "name" : "patterns",
                                                                                                "rating" : 5
                                                                                            },
                                                                                        }

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

                                                                                        Это касается любого формата, будь то хоть HAL, хоть OData, хоть JSONAPI, — требуется адаптер, предоставляющий API для работы с протоколом.

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

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

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

                                                                                        Я вроде бы прямо сказал, что так и задумано. В соответствии со спецификацией.
                                                                                        • 0
                                                                                          А как понять, что «status»: «shipped» — это одно из значений перечисления [ «shipped», «processing», «cancelled» ], а не произвольная строка?

                                                                                          Никак. Но это и не семантика, внезапно. Семантика значения shipped — это status.

                                                                                          Для передачи семантики, необходима расширенная система типов

                                                                                          Вы путаете семантику с типизацией.

                                                                                          HAL же вводит лишь один единственный дополнительный тип «список ссылок на другие сущности». То есть не решает проблему систематично, а не понятно зачем вставляет частичный костыль.

                                                                                          HAL решает конкретную проблему — представления гипермедиа. Зачем ему решать все остальные?

                                                                                          Это касается любого формата, будь то хоть HAL, хоть OData, хоть JSONAPI, — требуется адаптер, предоставляющий API для работы с протоколом.

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

                                                                                          Я вроде бы прямо сказал, что так и задумано. В соответствии со спецификацией.

                                                                                          Ну то есть задумано, что когда я пойду по первому идентификатору (search=MVC/proj=habr), не покидая контекст документа (/search=MVC/proj=habr), я попаду в /search=MVC/search=MVC/proj=habr, и так до бесконечности?
                                                                                          • 0
                                                                                            Не вижу принципиальной разницы между "типизацией" и "семантикой" в данном контексте.

                                                                                            В том и проблема, что HAL — это костыль для частного случая. Но проблему в общем случае он не решает. А общее решение типа того, что я привёл, помогает и в этом частном случае.

                                                                                            Вы видимо не заметили моё замечание "да, слэша вначале идентификаторов не хватает". Как вариант — вместо слеша использовать, например, точки.
                                                                                            • 0
                                                                                              Не вижу принципиальной разницы между «типизацией» и «семантикой» в данном контексте.

                                                                                              А зря.

                                                                                              В том и проблема, что HAL — это костыль для частного случая.

                                                                                              Для HATEOAS — с которого начался разговор — это как раз не частный случай, а фундаментально необходимая информация.
                                                                                              • –1
                                                                                                А может это вы видите то, чего нет?

                                                                                                Это частный случай понимания смысла. Решается выдачей схемы. Как $metadata в OData.
                                                                                                • 0
                                                                                                  А может это вы видите то, чего нет?

                                                                                                  Вряд ли. У price и discount один и тот же тип (до тех пор, пока у вас не type-driven development, по крайней мере), но разная семантика.

                                                                                                  Решается выдачей схемы.

                                                                                                  … и теперь нам надо придумывать парсер и интерпретатор для схемы вместо того, чтобы использовать зафиксированную в медиа-типе конвенцию. Избыточно.
                                                                                                  • 0
                                                                                                    Есть разные уровни семантики. И на разных уровнях они могут иметь как разный смысл, так и одинаковый. А типы просто гарантируют, что смысл не будет перепутан.

                                                                                                    Не избыточно, а расширяемо. Вы как обычно переусложняете :-)
                                                                                                    • 0
                                                                                                      А типы просто гарантируют, что смысл не будет перепутан.

                                                                                                      И как в приведенном примере типы гарантируют, что смысл не будет перепутан?

                                                                                                      Не избыточно, а расширяемо. Вы как обычно переусложняете

                                                                                                      Как раз наоборот: расширяемость без необходимости — это и есть переусложнение.
                                                                                                      • 0
                                                                                                        Если мы не вводим отдельные типы для price и discount то, очевидно, никак. В JSON с типами всё плохо, но мы уже об этом спорили в обсуждении формата Tree. JSON позволяет поднять уровень лишь через костыли в духе { _links": { "items": [{ "href": "box=123" }] } } — тут мы фактически вводим тип "ссылка", правда не конкретизируем ссылка это на какой тип. Просто динамическая ссылка на произвольную сущность.

                                                                                                        Необходимость-то есть. А вот понимания этой необходимости зачастую нет, да. Это я вам как фронтенд разработчик заявляю — у нас есть острая необходимость знать схему запроса/ответа, причём здесь и сейчас, а не где-то там, в сторонке, на другом сервере, для другой версии, с корявым описанием.
                                                                                                        • 0
                                                                                                          Если мы не вводим отдельные типы для price и discount то, очевидно, никак.

                                                                                                          Вот поэтому типизация и отличается от семантики.

                                                                                                          тут мы фактически вводим тип «ссылка», правда не конкретизируем ссылка это на какой тип

                                                                                                          Это ссылка не на тип, а на ресурс. Там вообще могут быть не объекты.

                                                                                                          Это я вам как фронтенд разработчик заявляю — у нас есть острая необходимость знать схему запроса/ответа,

                                                                                                          О, уже необходимость схемы, а не необходимость расширяемости. А я вам отвечу: мне, как разработчику клиентской или серверной части при сервисном взаимодействии необходимо знать, что запрос/ответ совпадают с заранее определенной схемой, и схема самого запроса/ответа мне в этом контексте нужна слабо.
                                                                                                          • 0
                                                                                                            Тип — это формализованная семантика.

                                                                                                            Ресурс какого-то типа. Давайте заканчивать этот очередной бессмысленный терминологический спор.

                                                                                                            Расширяемой разработчиком схемы, а не выбитой в граните спецификации как HAL. Хорошо, наверно, жить в мире, где схемы никогда не меняются. ;-)
                                                                                                            • 0
                                                                                                              Тип — это формализованная семантика.

                                                                                                              Далеко не во всех языках. В JS, например, точно не так.

                                                                                                              Ресурс какого-то типа

                                                                                                              А вот тип ресурса в HAL прекрасно указывается: пара атрибутов type/profile.

                                                                                                              Расширяемой разработчиком схемы, а не выбитой в граните спецификации как HAL.

                                                                                                              HAL специфицирует конкретную часть JSON-документа. Все прочие части вы вольны расширять как угодно. Вам не нравится HAL-овская трактовка ссылок? Окей, ваше дело. Предложите другую, семантически совпадающую с HATEOAS. Не нравится HATEOAS? Снова ваше дело, только что ж вы тогда удивляетесь, что о вашей реализации говорят, что она не HATEOAS?
                                                                                                              • 0
                                                                                                                Это от разработчика зависит а не языка.

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

                                                                                                                Вот, что я говорю: "HATEOAS — несколько неудобная реализация той же идеи.". Я не считаю необходимым ссылки на другие ресурсы выделять как-то особо, относительно остальных типов.
                                                                                                                • 0
                                                                                                                  Это от разработчика зависит а не языка.

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

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

                                                                                                                  Зато каждая ссылка самодостаточна.

                                                                                                                  HATEOAS — несколько неудобная реализация той же идеи.

                                                                                                                  Какой "той же"?
                                                                                                                  • 0
                                                                                                                    Пользовательские типы есть везде. И в JS в том числе.

                                                                                                                    Чтобы она была полностью самодостаточна, можно тут же приложить ещё и содержимое ресурса, на который она ссылается. ;-)

                                                                                                                    Я и не утверждал, что у меня HATEOAS. Идея hypermedia.
                                                                                                                    • 0
                                                                                                                      Пользовательские типы есть везде. И в JS в том числе.

                                                                                                                      Угу. Пожалуйста, выразите с помощью типов на JS следующее: у типа "заказ" есть два свойства: цена и скидка, каждое своего типа (т.е. значение цены нельзя присвоить в скидку, значение скидки нельзя присвоить в цену).

                                                                                                                      Чтобы она была полностью самодостаточна, можно тут же приложить ещё и содержимое ресурса, на который она ссылается.

                                                                                                                      Тогда она перестанет быть ссылкой и станет включенным ресурсом. Которые, что характерно, в HAL есть.

                                                                                                                      Я и не утверждал, что у меня HATEOAS.

                                                                                                                      Но зачем-то попросили объяснить, почему я тоже считаю, что у вас его нет.
                                                                                                                      • 0
                                                                                                                        class Scalar
                                                                                                                        {
                                                                                                                            constructor( value )
                                                                                                                            {
                                                                                                                                this.value = value
                                                                                                                            }
                                                                                                                        
                                                                                                                            static ensure( value )
                                                                                                                            {
                                                                                                                                if( value instanceof this ) return value
                                                                                                                                throw new Error( 'Wrong type' )
                                                                                                                            }
                                                                                                                        }
                                                                                                                        
                                                                                                                        class Price extends Scalar {}
                                                                                                                        
                                                                                                                        class Discount extends Scalar {}
                                                                                                                        
                                                                                                                        class Order()
                                                                                                                        {
                                                                                                                            _price = null
                                                                                                                            get price()
                                                                                                                            {
                                                                                                                                return this._price
                                                                                                                            }
                                                                                                                            set price( value )
                                                                                                                            {
                                                                                                                                this._price = Price.ensure( value )
                                                                                                                            }
                                                                                                                        
                                                                                                                            _discount = null
                                                                                                                            get discount()
                                                                                                                            {
                                                                                                                                return this._discount
                                                                                                                            }
                                                                                                                            set discount( value )
                                                                                                                            {
                                                                                                                                this._discount = Discount.ensure( value )
                                                                                                                            }
                                                                                                                        
                                                                                                                        }
                                                                                                                        
                                                                                                                        var order = new Order
                                                                                                                        order.price = new Price
                                                                                                                        order.discount = order.price

                                                                                                                        Объявления свойств можно упростить до:

                                                                                                                        class Order()
                                                                                                                        {
                                                                                                                            @Price.prop()
                                                                                                                            _price = null
                                                                                                                        
                                                                                                                            @Discount.prop()
                                                                                                                            _discount = null
                                                                                                                        }

                                                                                                                        Не стоит приписывать мне то, что я не говорил.
                                                                                                                        • 0
                                                                                                                          (а) Это JavaScript? Или все-таки ES6?
                                                                                                                          (б) вы не выразили нужное ограничение с помощью типов, вы были вынуждены написать в каждом свойстве проверку на совпадение (с равным успехом можно использовать тегированные структуры)
                                                                                                                          • 0
                                                                                                                            1) Версия языка не имеет значения, в рантайме они полностью совместимы.
                                                                                                                            2) Вам шашечки или ехать? Можете записать так, если очень хочется, чтобы было похоже на какой-нибудь C#:

                                                                                                                            class Order
                                                                                                                            {
                                                                                                                                @Price price = null
                                                                                                                                @Discount discount = null
                                                                                                                            }
                                                                                                                            • 0
                                                                                                                              Вам шашечки или ехать?

                                                                                                                              В данном случае мне именно шашечки. Потому что когда речь идет о системе типов, то я ожидаю от нее вполне конкретных свойств. Когда мне надо ехать, я и без системы типов обойдусь.
                                                                                                                              • 0
                                                                                                                                Ну то есть кроме статической типизации никакой иной не бывает. Ок.
                                                                                                                                • 0
                                                                                                                                  Скажем так, в типизации, отличной от статической, сложно добиться того, чтобы типы — а не объекты — имели семантику.
                                                                                                                  • 0
                                                                                                                    Вот, что я говорю: «HATEOAS — несколько неудобная реализация той же идеи.». Я не считаю необходимым ссылки на другие ресурсы выделять как-то особо, относительно остальных типов.

                                                                                                                    Anyway, мне не очень интересно, что вы думаете о HATEOAS, я комментировал тот факт, что у вас — не он.
                                                                                • +2
                                                                                  чтобы не раздувать объём выдачи

                                                                                  gzip сверху и не нужно об этом париться.

                                                                                  По поводу "извращенности" — я могу понять обиды пользователей вашего API, поскольку нельзя просто взять и использовать API. Я не вижу профита конкретно в такой реализации, против обычных урлов. В случае урлов сервер полностью отвечает за то, где искать ресурсы. А тут выходит что клиент тоже должен что-то знать об этом.

                                                                                  Ну то есть сама идея более чем правильная, а вот реализация — заставляет писать велосипеды. А мне что-то как-то уже надоело велосипеды писать. Вот есть например jsonapi — вполне себе годный формат. Единственное что под android неудобно использовать, во всяком случае имеющаяся реализация под Java так себе.
                                                                                  • 0
                                                                                    Тут дело больше в удобстве отладки. Копипаста имени сервера только лишь создаёт визуальный шум.

                                                                                    Я же написал, что эту фичу не удалось протолкнуть. Так что в реальности было всё ещё хуже — клиенты брали идентификаторы сущностей вида "12345" и подставляли в шаблон вида https://example.com/person={personId}?fetch={fetchPlan}.

                                                                                    Вычисление абсолютной ссылки на основе относительной и базовой есть в любой библиотеке для работы со ссылками. Это базовая операция, которая должна применяться ко всем ссылкам из документа. Так устроен веб.
                                                                                    • 0

                                                                                      Я тут запилил пример того, то я имею ввиду, правда с XML выдачей, но формат легко меняется на любой другой: http://nin-jin.github.io/harp=1/

                                                              • +2
                                                                Мда, тёмные времена наступают ((
                                                                • +6
                                                                  Он не масштабируется под реальные приложения как Дэн любит утверждать. Давайте рассмотрим простой пример, объясняющий почему.

                                                                  А кто ни будь понял почему? Я никаких объяснений не увидел. Поток сознания какой то.
                                                                  • +1
                                                                    Читая статью я сделал вывод что главное отличие в этом:
                                                                    Другими словами, машины состояния не должны быть кортежами, соединяющими два состояния (S1, A, S2) каковыми они обычно являются. Они являются, скорее, кортежами форм (Sk, Ak1, Ak2,...) которые определяют все разрешенные Actions для состояния Sk с результирующим состоянием вычисляемым после того, как Action был применен к системе и Model обработал изменения.

                                                                    И выведенном далее отдельном state representation.

                                                                    На всякий случай написал еще автору оригинальной статьи и попросил его сформулировать, в чем проблема масштабируемости. Как только он ответит — репостну сюда.
                                                                    • +3
                                                                      А что за термин такой «машина состояний»?

                                                                      И какое отношение это имеет к MVC? Если имеется ввиду finite-state machine, то вообще-то у модели может быть бесконечное много состояний, т.к. это может быть, например, дерево каталогов.
                                                                      • +1
                                                                        Да, речь именно про finite state machine.
                                                                        Комбинация с MVC и предлагается автором, то есть он предлагает оценить нашу систему с точки зрения состояний (выделить их в отдельный конструктив) и на их основе вычислять next-action predicate.

                                                                        По поводу дерева каталогов и бесконечного количества состояний не совсем понял. Можете более развернуто описать? Я могу представить состояния к примеру, «редактируемый-нередактируемый», или «актуальный каталог-каталог в архиве», но бесконечного количества состояний я не могу представить.
                                                                        • +1
                                                                          Ок, спасибо, теперь более понятно в каком контексте это используется.

                                                                          Ну вообще-то Model в MVC может иметь сколь угодно много состояний и сколь угодно много переходов, более того переходы и состояния могут наращиваться по мере исполнения программы. Плюс Model может менять свои состояния без пользователя. Все это категорически не укладывается в FSM.
                                                                          • +1
                                                                            В MVC модель и есть наш FSM и количество состояний у нас так или иначе детерминировано. Контроллер может быть всего-лишь одним из источников данных на вход модели, а решение менять состояния или нет принимать только ей.
                                                                            • +2
                                                                              Состояния конечно можно придумать, да, но FSM тем и отличается от произвольной transition system тем что имеет некоторые ограничения, которые выливаются в ряд интересных и полезных теорем. В общем случае эта теория не применима к обычным моделям.

                                                                              Т.е. все эти паттерны, и вся эта теория ООП тем хороша что не накладывает никаких технических ограничений, только семантически, но сводить Model к FSM это какой-то Erlang получается.
                                                                    • +1
                                                                      Автор ответил на вопрос по поводу масштабируемости:
                                                                      Оригинал
                                                                      There are a couple of reasons why Redux «does not scale to real-world applications» (which is an expression that Dan Abramov is using all the time to explain that Redux is so much better).

                                                                      First, Redux cannot implement the simple rocket example because it does not have a «next-action-predicate», so when the counter is decremented and something has to decide what to do next, Redux, is powerless, some ugly code will show up in the reducer.

                                                                      Second, Redux is coupling action/model inside the reducer. Let's say you trigger an action that increments a counter, all I suggest in SAM is that you factor the code in two steps:
                                                                      a) an action (a pure function) hat given a dataset computes the proposed changes to the model
                                                                      b) a method that mutates the model

                                                                      The Redux reducer looks like this:

                                                                      case INCREMENT:
                                                                                  return state + 1;
                                                                      

                                                                      All I am saying, you need to write an action increment:

                                                                      function increment(data) {
                                                                           data.counter = data.counter || 0 ;
                                                                           data.counter += 1 ;
                                                                           return data ;
                                                                      }
                                                                      

                                                                      and then present the new value to the model which will choose to accept the proposed values:

                                                                      model.present = function(data) {
                                                                             if (data.counter !== undefined) {
                                                                                 // can have some validation rules that decides whether
                                                                                 // that value is acceptable or not
                                                                                 model.counter = data.counter ;
                                                                             }
                                                                      }
                                                                      


                                                                      In SAM the actions and the model updates are strictly decoupled, unlike Redux which encourages people to create a big ball of mud, for no particular reason, other than Dan using a naive interpretation of state machine semantics.

                                                                      With SAM, actions are external to the model and as such can be reused across models and even implemented by third parties. In Redux, actions are merely intents. The implementation of the action is in the model (reducer). That is wrong.

                                                                      Перевод
                                                                      Есть несколько причин почему Redux “не масштабируется под реальные приложения” (это выражение, которое Дэн Абрамов постоянно использует чтобы объяснить, что Redux гораздо лучше)

                                                                      Во-первых, Redux не в состоянии реализовать простой пример с ракетой, потому что он не имеет “next action predicate”. То есть когда счетчик уменьшается и нужно принять решение, что делать дальше, то Redux бессилен и в reducer появится какой-либо уродливый код.

                                                                      Во-вторых, Redux связывает действия и модели внутри reducer. Предположим, вы вызываете действие которое увеличивает счетчик, все, что я предложил в SAM это что разделить код на два шага:
                                                                      а) действие (чистая функция)
                                                                      b) метод, который изменяет модель

                                                                      Reducer в Redux выглядит следующим образом:
                                                                      case INCREMENT:
                                                                                  return state + 1;
                                                                      

                                                                      Все, что я утверждаю, это что вам необходимо написать действие инкрементации:
                                                                      function increment(data) {
                                                                           data.counter = data.counter || 0 ;
                                                                           data.counter += 1 ;
                                                                           return data ;
                                                                      }
                                                                      


                                                                      И после этого предоставить новой значение в Model, который решит, принять ли предлагаемое значение:
                                                                      model.present = function(data) {
                                                                             if (data.counter !== undefined) {
                                                                                 // can have some validation rules that decides whether
                                                                                 // that value is acceptable or not
                                                                                 model.counter = data.counter ;
                                                                             }
                                                                      }
                                                                      

                                                                      В SAM действия и обновления модели строго разделены, в отличие от Redux, который поощряет людей в создании big ball of mud (полагаю, автор про антипаттерн) без особых на то оснований, помимо этого Дэн использует наивную интерпретацию семантики машин состояний.

                                                                      В SAM, действия являются внешними по отношению к Model и, как таковые, могут быть переиспользованы с другими Model-ами и даже быть реализованными третьей стороной. В Redux, действия это всего лишь намерения. Реализация действий находится в модели (reducer). Это неправильно.
                                                                      • +1
                                                                        it does not have a «next-action-predicate», so when the counter is decremented and something has to decide what to do next, Redux, is powerless, some ugly code will show up in the reducer.


                                                                        И что с того? Redux реализует концепцию event sourcing, нас интересует только цепочка действий а не конкретное состояние системы, его можно всегда вычислить. А это означает что наши редьюсеры можно переписать и полностью поменять модель, описывающую состояния системы в целом, при этом мы не теряем данные.

                                                                        Redux is coupling action/model inside the reducer

                                                                        Опять же, по сути redux это именно редьюсеры и ничего более. «экшены» — это команды, юзкейсы. Конечно же мы должны знать о них. По сути и редьюсеры и экшены это часть модели, детали ее реализации. Подробнее можно у Грэга Янга почитать/посмотреть лекции.

                                                                        All I am saying, you need to write an action increment:


                                                                        Это прямое нарушение идеи, заложенной в Redux, мы тут мутируем состояние, и теперь зависим от последовательности действий и от времени. Экшены ни в коем случае не должны мутировать состояния (как и контроллеры в MVC). Они должны «просить» модель сделать что-то, в нашем случае — экшены нужны что бы запомнить «что мы хотели сделать», а редьюсеры уже смогут вычислить состояния не основе цепочки действий. Декремент в этом случае это будет просто какая-то операция, но значения мы не будем сохранять, так как мы не знаем какое оно было изначально.

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

                                                                        Именно в этом смысл, если у нас нет прямых мутаций состояний (как в FSM) а только история переходов — то нет никаких проблем.
                                                                    • +10
                                                                      В MVC такая логика будет реализована в Controller, и, вероятно, вызываться по таймеру из View.

                                                                      Что?! Таймер во View? Это в MVC? Это косяк перевода или там так и написано?
                                                                      • +4
                                                                        In MVC, that kind of logic would be implemented in the controller, perhaps triggered by a timer in the view.

                                                                        Ну тут на самом деле вопрос. Если это чисто UI штука (т.е. через 10 секунд мы отправляем команду), то может быть и так.
                                                                        Но если эти 10 секунд есть в предметной области (т.е. там «протяжка, предварительная, ключ на старт»), то таймер должен быть строго внутри модели.
                                                                        Впрочем, я испорчен M-V-VM.
                                                                      • +1
                                                                        Всегда считал что что MVC в вебе — как на корове седло. Нет ничего естественнее компонентного подхода. Никаких API, никакой асинхронки (и уж тем более яваскриптового безумия в виде клиентских фреймворков), никаких проблем с сохранением состояния страницы после перезагрузки. И уж тем более нет необходимости изобретать мудреные конструкции аналогичные приведенным в статье.
                                                                        • +1
                                                                          никаких проблем с сохранением состояния страницы после перезагрузки.

                                                                          Правда?
                                                                          • +8
                                                                            Разумеется нет — просто хотел всех обмануть.
                                                                            • +1
                                                                              Ну вот то-то и оно. Хранение состояния в вебе — одна из самых сложных задач (понятное дело, после инвалидации кэшей и именования).
                                                                              • +1
                                                                                это был сарказм. Не вижу ни малейших проблем с сохранением состояния (разумеется не в случае MVC). Но не собираюсь обсуждать с вами это по десятому кругу — у вас спорить на любую тему — это просто хобби.
                                                                                • +1
                                                                                  Не вижу ни малейших проблем с сохранением состояния

                                                                                  Не видите? А как вы собираетесь хранить состояние в случае веб-фермы?
                                                                                  • +1
                                                                                    Если на вашей платформе что-то сложно реализуется, это говорит о проблеме в вашей архитектуре
                                                                                    • +3
                                                                                      Что-то у меня на SQL-сервере плохо 3D-графика обсчитывается, наверное, у меня в архитектуре проблема, пойду, поправлю…
                                                                                      • +2
                                                                                        Значит вы не на ту технологию сложили ту самую архитектуру
                                                                                        • +3
                                                                                          … осталось применить этот же тезис к исходному комментарию.
                                                                                    • +1
                                                                                      вариантов масса — у .NET WebForms один способ, а например у Java Wicket другой — не вижу тут великой проблеммы
                                                                                      • +1
                                                                                        Не возьмусь утверждать за Wicket, а у WebForms с этим состоянием достаточное количество проблем, чтобы приходилось постоянно искать пути их решения.
                                                                                        • +1
                                                                                          именно с состоянием проблем нет.

                                                                                          Главная проблема WebForms — это как ни странно визуальный редактор. Изза него разрабу очень неудобно управлять стилями и элементами.
                                                                                          Поэтому в .net MVC никакого визуального построителя нет. Все что майкам надо было сделать — выкинуть редактор и оставить компонентную модель.

                                                                                          • +1
                                                                                            именно с состоянием проблем нет.

                                                                                            Правда? То есть вы считаете, что ресурсы, уходящие на поддержку состояния 100+ контролов на странице — это не проблема?

                                                                                            Главная проблема WebForms — это как ни странно визуальный редактор.

                                                                                            А кто-то заставляет им пользоваться?
                                                                                    • +3
                                                                                      разумеется не в случае MVC


                                                                                      За состояние в MVC полностью отвечает модель. Этот же слой инкапсулирует в себе всю логику по изменению состояния. C и V — только UI, через него мы только взаимодействуем с приложением, но они какак не должно влиять на состояние приложения (менять напрямую).

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

                                                                                      А если у вас не вызывает проблем сохранение состояния, то вообще плевать какую мы архитектуру для UI используем.
                                                                                      • +1
                                                                                        речь о состоянии страницы в браузере — то есть о персистентности ее элементов после перезагрузки.

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

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

                                                                                        • +1
                                                                                          Именно эта проблема вынудила разрабов использовать клинтские фрейморки, асинхронные вызовы и прочую ересь что многократно усложнило разработку.

                                                                                          Нет, не эта — а желание ускорить отклик интерфейса на действия пользователя.

                                                                                          А к примеру в WebForms такой необходимости нет — чекер никуда не сбросится.

                                                                                          Никогда не задумывались, какой ценой?
                                                                                          • +1
                                                                                            Нет, не эта — а желание ускорить отклик интерфейса на действия пользователя.

                                                                                            а какие проблемы с откликом? Что интернет медленее работает если webforms или база данных? Или браузер медленнее рисует?
                                                                                            И как MVC ускоряет отклик?
                                                                                            Никогда не задумывались, какой ценой?

                                                                                            нет никакой цены и нет никаких проблем кроме связаных с разметкой которую вынуждены формировать под работу визуального редактора
                                                                                            • +4
                                                                                              а какие проблемы с откликом?

                                                                                              Ну так, раундтрип до сервера — это всегда дольше, чем локально обработать. Три, четыре, пять порядков.

                                                                                              Что интернет медленее работает если webforms или база данных?

                                                                                              Страница быстрее работает, если интернет не задействовать, как ни странно.

                                                                                              И как MVC ускоряет отклик?

                                                                                              Речь шла не об MVC, а о клиентских фреймворках и асинхронии.

                                                                                              (Но вообще конкретно между asp.net MVC и asp.net WebForms — MVC «в среднем» быстрее, потому что нет накладных расходов на избыточное состояние. Понятно, что можно WebForms заточить, но это требует больше усилий.)

                                                                                              нет никакой цены

                                                                                              Правда? Вы никогда не смотрели на размер передаваемого viewstate? На то, как выглядит клиентский HTML? На то, сколько событий вызывается при разборе и актуализации состояния?
                                                                                              • +1
                                                                                                а какие проблемы с откликом?
                                                                                                А вы попробуйте использовать синхронные HTTP вызовы. Удивитесь.
                                                                                                • +2
                                                                                                  как разработчик пробую и те и другие. разницы практически никакой.
                                                                                                  вы удивитесь но на диалапе сейчас никто не сидит.
                                                                                                  А вот если не просто асинхронка а еще и модные нынче клиентские фреймворки с сотнями евентов и биндингов то отклик однозначно больше, особенно на мобильных браузерах.
                                                                                                  • +3
                                                                                                    А вот если не просто асинхронка а еще и модные нынче клиентские фреймворки с сотнями евентов и биндингов то отклик однозначно больше, особенно на мобильных браузерах.

                                                                                                    У меня создается впечатление что вы просто не знаете о чем говорите. Что до "нет разницы между синхронными и асинхронными запросами" — попробуйте сделать синхронный ajax запрос и запустить анимацию одновременно. А если вам так не нравятся промисы/колбэки, так есть async/await.

                                                                                                    try {
                                                                                                        response = await myService.myAsyncCall()
                                                                                                    } catch (e) {
                                                                                                        // handle exception
                                                                                                    }
                                                                                                    • 0
                                                                                                      вообще то речь шла о клиенте.
                                                                                                      если речь о сервере то в большинстве случаев ощутимого выиграша тоже нет.
                                                                                                      данная конструкция исправляет кривую архитектуру IIS не более того.
                                                                                                      но с учетом скорости передачи данных и рендеринга в браузере это не имеет значения для конечного пользователя. Разве что на очень высоко нагруженых проектах но такие проекты составляют слишком незначительную долю всех сайтов чтобы говорить о каких то типовых решениях .
                                                                                                      • +2
                                                                                                        вообще то речь шла о клиенте.

                                                                                                        А я вам о чем?

                                                                                                        если речь о сервере то в большинстве случаев ощутимого выиграша тоже нет.

                                                                                                        Предлагаю вам эксперемент. Запустить ab на ваше node.js приложение которое делает "синхронные" вызовы. И посмотрите какое количество запросов просто отвалится.

                                                                                                        данная конструкция исправляет кривую архитектуру IIS не более того.

                                                                                                        Причем тут IIS?

                                                                                                        Резюмирую — вы не знаете о чем говорите, раз не знаете что в JS уже можно пользоваться штуками вроде async/await.
                                                                                                        • +1
                                                                                                          так вот об ощутимом "выиграша", благодаря ему как раз становится возможной полноценная асинхронная работа с IO операциями в аспнете. Я специалист не сильно широкого круга, можете привести пожалуйста примеры асинхронных платформ для веба кроме ноджс?
                                                                                                          • –1
                                                                                                            вообще то смысл ноды изначально как раз однопоточная работа 0 асинзронное обращение с клиента не меняет сути дела нода там или нет
                                                                                                            хотя строго говоря ajax это тоже не асинхронные вызовы — технически правильно — паралельные вызовы в фоновом режиме или как то так

                                                                                                            а в яве проблем с асинхронкой не было никогда — любой сервлет работает в отдельном потоке в ява машине

                                                                                                            то что происходит в аспнете это исправление работы iis который изначально работал как нода -в одном потоке.

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

                                                                                                            Не вижу тут великого достижения.
                                                                                                            • +2
                                                                                                              вообще то смысл ноды изначально как раз однопоточная работа

                                                                                                              Нет, суть ноды была в том что бы запустить однопоточный event loop и организовать эффективную работу с I/O. А эффективная работа с I/O заключается в максимальной утилизации CPU, что возможно только в рамках одного потока и с использованием неблокирующих вызовов.

                                                                                                              любой сервлет работает в отдельном потоке в ява машине

                                                                                                              Погуглите про "переключение контекста". Именно по этой причине nginx работает на event loop, а не на потоках, несколько процессов-воркеров с event loop.
                                                                                                              • 0
                                                                                                                Но асинхронные сервлеты были ещё до ноды, как и NIO. И нода и NIO используют epoll (на линуксе), так что особо разницы нет.
                                                                                                                А производительность, тем не менее, у java всё равно обычно выше даже при использовании обычных сервлетов без NIO.
                                                                                                      • +3
                                                                                                        как разработчик пробую и те и другие. разницы практически никакой.
                                                                                                        Практически?! Я как пользователь ненавижу таких разработчиков как вы.

                                                                                                        вы удивитесь но на диалапе сейчас никто не сидит.
                                                                                                        У меня сейчас не самый худший интернет. Если посмотреть на загрузки на данной странице хабра, то одним из самых быстрых запросов окажется, например, ваша аватарка. 1.3кб за 45мс (403 ответ).

                                                                                                        Конечно рядовой пользователь скорее всего и не заметит, что приложение не отвечает 45 миллисекунд после нажатия какой-либо кнопки. Но ощущения от приложения точно поменяются.

                                                                                                        И при этом это самый маленький, требующий минимум серверной логики, запрос.
                                                                                                        • +1
                                                                                                          Конечно рядовой пользователь скорее всего и не заметит, что приложение не отвечает 45 миллисекунд после нажатия какой-либо кнопки. Но ощущения от приложения точно поменяются.

                                                                                                          каким образом они поменяются если он не заметит?
                                                                                                          и как асинхронка ускорила бы загрузку страницы хабра?
                                                                                                          можно конено сделать чтобы коменты подтягивались как в фейсбуке но это резко усложнит разработку за что надо кому то платить, при том что как рядовой пользователь я не вижу никаких проблем с хабром. а вот если у меня на мобиле будет дико тупить браузер потому что на сайт навесили какой то ангуляр — уж точно буду матерится.
                                                                                                          • +1
                                                                                                            Отзывчивость интерфейса имеет большое значение.

                                                                                                            С хабром нет никаких проблем. И, к слову, тут все запросы работают асинхронно.

                                                                                                            Про комментарии в FB мне не понятно. Т.к. не пользуюсь этим медленным и неудобным сайтом.

                                                                                                            В клиентском вебе всё асинхронное. Кому тут что платить за какую-то сложность? Признайтесь, вы просто очень далеки от данной темы.

                                                                                                            Про ангуляр согласен. Он годится только для простых сайтов. Как только вам нужно сделать что-то посложнее тут же начинаются пляски с бубном.
                                                                                                            • +1
                                                                                                              и как асинхронка ускорила бы загрузку страницы хабра?

                                                                                                              Очень просто: запросы не будут блокировать работу со страницей.
                                                                                                  • +3
                                                                                                    то есть о персистентности ее элементов после перезагрузки.


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

                                                                                                    что многократно усложнило разработку.

                                                                                                    На самом деле лишь уравновесило. Сейчас для процентов так 80% проектов апишку можно просто сгенерить. А для случаев посложнее, «отчищение» бэкэнда от обязанностей формирования представления для человека только упрощает дело (отдать json-структурку проще чем замэпить ее на HTML, учитывать всякие ajax и работать с формами). Чистая клиент-серверная архитектура.

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

                                                                                                    А на архитектуру не плевать.

                                                                                                    Архитектура — это весьма емкое слово. Конкретно MVC мне больше нравится воспринимать как «прием» для отделения UI от приложения (если они отдельно друг от друга — у нас больше пространства для моневра опять же).

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


                                                                                                    Ммм… у меня закралось подозрение маленькое… Можете все же описать мне что такое это ваше MVC что оно так отличается от моего?
                                                                                          • 0
                                                                                            Парень наверное на state-less контроллеры намекает ;)

                                                                                            Заголовок спойлера
                                                                                            Который раз смотрю ваши коментарии и убеждаюсь, что тред с вашим коментом скатится в обсуждение какого-то древнего легаси с которым вы вынуждены работать. Что ж вы делаете то там???

                                                                                            • 0
                                                                                              Который раз смотрю ваши коментарии и убеждаюсь, что тред с вашим коментом скатится в обсуждение какого-то древнего легаси с которым вы вынуждены работать. Что ж вы делаете то там?

                                                                                              Вы меня ни с кем не путаете?