Pull to refresh
18
0

User

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

Что значит тупо отклоняются? Как сервер поймет что полученный запрос это повторная отправка одного и того же запроса (после обрыва сети) а не создание нового заказа? Смотрите тут проблема чисто логическая — если есть приложение где юзер может создавать что-то много раз неважно будут ли это поездки или задачи в таск-менеджере или комментарии — всегда возможна ситуация обрыва связи при котором возможны два варианта — а) запрос не успел дойти до сервера и тогда можно безопасно выполнять его повторную отправку б) запрос дошел до сервера и был обработан но обрыв связи произошел на обратном пути — и тогда повторная отправка уже будет небезопасна так как сервер посчитает что это был отдельный запрос и получим дубль. А клиент получив ошибку обрыва соединения никак не сможет понять был ли обрыв связи на пути к серверу или на обратном пути.
И получается что в итоге возможны 3 варианта действий со стороны клиента — а) не пытаться ретраить запрос и понадеяться на успешную доставку записав в издержки редкие потери — это то соответствует семантике «at most once» в кафке или других брокерах сообщений б) при обрыве сети просто ретраить запрос, а в издержки записать возможные дубли при редких случаях когда связь обрывается на обратном пути — это соответсвует семантике «at least once» в) семантика «exactly once» — выполнять ретрай при обрыве связи но с каждым запросом передать серверу дополнительную информацию при котором сервер сможет различить был ли такой запрос обработан или нет чтобы тогда сервер отклонил этот запрос (либо возможен еще вариант когда перед ретраем происходит получение информации из сервера чтобы уже сам клиент без лишней отправки запроса отменил запросы которые успели обработаться)
В зависимости от того что взять в качестве этой дополнительной информации возможны различные подходы:
1) сгенерировать на клиенте рандомный айдишник для нового заказа (guid или uuid) чтобы при повторном запросе база данных выдала ошибку что такой заказ по такому айдишнику уже есть
2) передавать такой же сгенерированный айдишник (ключ идемпотентности) но который не будет сохраняться в качестве primary id а будет храниться где-то в другом месте (но поскольку это накладные расходы то обычно хранят ограниченное время)
3) сервер может не хранить айдишник для каждого запроса/заказа но тогда клиент должен с каждым запросом передавать список айдишников/хеш-заказов всех предыдущих заказов, еще есть вариант — передача версии списка заказов
4) обобщая предыдущий способ — при передаче версии списка заказов или хеша происходит по сути выстраивание запросов клиента в очередь — то есть параллельно отправленные запросы клиента приведут к ошибке так как не совпадут версии/хеши. А в общем случае если у нас не одна сущность «заказы» а сложное crm-приложение с кучей сущностей то придется хранить по айдишнику/хешу версии на каждую таблицу либо можно хранить только один айдишник/хеш по всем запросам для одного юзера (точнее его сессии так как у юзера может быть залогинен с несколько устройств) а на клиенте выстраивать все запросы по всем сущностям в очередь
И получается что минимизируя количество дополнительной информации которую должен хранить сервер мы приходим к тому что в случае ухудшаем производительность при использовании http-протокола — теперь нужно дожидаться результата предыдущего запроса http-запроса прежде чем отправить новый.
Но это в случае http, в случае же вебсокетов — ситуация иная — вебсокеты гарантируют последовательность а значит что отправленные запросы на сервер в одном вебсокет-соединении будут приходить в том же порядке в котором был отправлены из клиента. А это значит что мы можем отправлять запросы сразу же не дожидаясь ответа предыдущего а в случае восстановления сети после обрыва связи достаточно получить от сервера айдишник последнего обработанного запроса и таким образом понять какие запросы успели обработаться и их следует удалить из очереди а какие нужно будет отправить повторно

Мне кажется вы неправильно понимаете проблемы redux
Представьте сложное spa-приложение, какую-нибудь навороченную админку где юзер может одновременно открыть и расположить по экрану кучу панелей, окошек, cлайдеров, табов, всплывающих попапов с детализацией и т.д. которые будут отображать информацию об одних и тех же данных только с разными фильтрами, сортировкой, аггрегацией, детализацией и т.п. И теперь допустим в отдельной форме меняется какой-то один объект-сущность и нужно обновить все места которые отображают данные этого объекта. И появляется вопрос как определить какие компоненты отображают в текущий момент информацию об объекте который изменился чтобы выполнить перерендер (diff) только этих компонентов а не всего приложения?


С редаксом можно этого достичь только если законнектить каждый компонент и делать полностью плоский стейт где будет соответствие {[айдишник]: объект} а
все one-to-many и many-to-many связи моделировать через айдишники. Это нужно делать потому что независимо от того во скольких местах интерфейса отображается сущность — если данные о ней будут храниться только в одном месте то это избавляет от проблемы ручной синхронизации и необходимости применять изменение во многих местах. А ручная синхронизация еще хуже — вот статья на тему сложности синхронизации — https://hackernoon.com/data-denormalization-is-broken-7b697352f405 — там правда про базу данных на бекэнде но это соответствует модели редакса когда на одно событие может реагировать много редюсеров).
И в результате с редаксом даже с нормализованным стором получается ужасно неудобное решение потому что придется вручную джойнить данные во вьюхах (точнее в mapStateToProps) и обработчиках и любая бизнес-логика где необходимо обращаться к связанным сущностям будет сплошь и рядом пронизана вытаскиванием нужных объектов по их айдишникам.


А вот mobx позволяет не делать нормализацию а хранить объекты в их древовидно-графовом виде. То есть если например есть много таблиц связанных one-to-many или many-to-many то не нужно никаких айдишников — объекты можно прямо связывать ссылками друг на друга. Например если есть приложение где пользователь может создавать папки а внутри папок проекты а внутри проектов задачи и внутри них комментарии то эту схему можно один в один отобразить на объектах — будет объект пользователя в котором будет находиться массив папок ({firstName: "...", email: "", folders: [{..}, {...}, {...}]}), и каждая папка будет хранить объект проекта который будет хранить вложенный массив проектов ( {name: "folder1", projects: [{..}, {..}, {}]}, где каждый объект проекта будет хранить также массив ссылок на вложенные задачи а задача хранить вложенный массив комментариев и т.д, причем еще удобно делать обратные ссылки на родительские объекты чтобы можно было удобно обращаться через точку (например project.folder.priority) а не передавать через пропсы или контекст что требует изменений родительских компонентов.
И таким образом можно удобно работать с данными моделируя связи через ссылки (включая циклические связи между объектами когда вложенный объект имеет ссылку на родителя или в случае many-many связей когда например у проекта может быть много пользователей) и все это позволяет обращаться к нужным сущностям через точку без необходимости джойнить их по айдишникам как в redux.
Вот сравните как "удобно" обратиться к родительской папки из комментария (сквозь промежуточные объекты задачи и проекта) в редаксе когда мы делаем это через айдишники


state.folders[state.projects[state.tasks[comment.taskId].projectId].folderId].priority

А теперь как это проще и наглядней можно делать c mobx


comment.task.project.folder.priority

И mobx при этом еще и обеспечивает точечное обновление компонентов без каких-либо неудобств (достаточно только обернуть поля объектов декоратором observable а сами компоненты декоратором observer) и с большей эффективностью — в отличие от редакс ему не нужно делать цикл по всем подключенным компонентам (чтобы в mapStateToProps сравнить новый и старый объект и понять нужно ли выполнять обновление компонента) — mobx при изменении объекта сразу знает какие компоненты нужно обновить так как хранит список этих компонентов на нужном поле объекта. И если произойдет изменение одного объекта то будет выполнено обновление только тех реактовских компонентов которые в текущий момент отображают этот объект. А если будет происходить изменение сразу многих полей или если компонент зависит то от одних данных то от других данных (всякие условия в шаблоне) то mobx умеет это оптимизировать и выполнять обновление в одном проходе и только тех компонентов которые зависят от нужных изменений в конкретный текущий момент (то есть компонент не будет лишний раз обновляться если поменялись данные в неактивном бранче условия)
В итоге юзеру не нужно думать в каких местах интерфейса одновременно находятся одни и те же данные при их изменении (сколько панелей/окошке открыто) и уж тем более не синхронизировать состояние отдельных компонентов вручную

Хм, сколько комментариев и никто не озвучил самый большой недостаток веб-компонентов — невозможность разбить на компоненты svg-элементы. Вот допустим вы хотите написать какое-то сложное приложение где будут графики, кривые безье и и.д. И если все эти отдельные отдельные svg-элементы можно динамически создавать, менять аттрибуты, добавлять обработчики и т.д (то есть все как с обычными html-элементами) то вот декомпозировать и разбить на отдельные веб-компоненты у вас не получится потому что кастомные элементы не поддерживают svg. В то время как используя любой из популярных фреймворков (реакт, ангуляр, vue и т.д) я могу разбить svg-верстку на отдельные компоненты добавив им поведение и инкапсулировать логику (то для чего были придуманы компоненты) то с веб-компонентами я это сделать не могу. Facepalm.
Exactly once требует согласованности продюсера и консьюмера. Если внешний апи на (на который происходит отправка смс) не предоставляет возможности передать локальный айдишник чтобы случае сбоя или обрыва связи проверить по нему произошла ли обработка запроса или нет то exactly onсe семантику в этом случае никак не получить. А kаfka гарантирует exactly once семантику только между своими же нодами.
Мне кажется в плане решения проблем с идемпотентностью http и rest вносят большую путаницу чем rpc. С rpc все запросы по умолчанию неидемпотентны и это заставило бы Васю уже на этапе проектирования задуматься о повторных запросах из-за плохой связи. И с rpc такая проблема решается даже проще чем c http, ее можно вообще решить на уровне транспорта и упростить бизнес-логику. С rpc можно использовать вебсокеты которые в отличие от http имеют строгую очередность — запросы приходят на сервер точно в таком же порядке в каком были отправлены от клиента. А это значит что нам не нужно хранить день или сколько там idempotency key от всех http-запросов клиента а достаточно хранить только айдишник последнего запроса клиента и потом когда клиент после разрыва связи снова соединится с сервером он должен в первую очередь загрузить этот айдишник и проверить совпадает ли он с последним отправленным запросом и если совпадает тогда отменить запросы которые ждут повторной отправки. И все это можно решить на уровне транспорта и автоматически ретраить запросы при обрыве связи а на уровне бизнес-логики будет просто отправка запроса и отображение крутилки пока этот запрос не выполнится
Интересно а как быстро будет работать такой сборщик мусора если допустим взять на амазоне сервер с 4 терабайтами оперативки и аллоцировать десять миллиардов объектов которые будут ссылаться друг на друга (реляции между сущностями)?
А у меня такой вопрос к сторонникам rest-подхода — допустим вы не можете использовать http по причинам слабой производительности и есть необходимость использовать вебсокеты для браузеров (либо tcp для мобильных клиентов) — будете ли вы кодировать в передаваемых сообщениях схему http протокола и дальше в своем привычном режиме организовывать взаимодействие клиента с сервером согласно вашим представлениям rest-а или может вы решите не кодировать в сообщениях всю спецификацию http а ограничить себя каким-то подмножеством или может даже изменить какие-то моменты, или может вообще не будете смотреть на спецификацию http-протокола и закодируете в сообщениях формат общения удобный клиенту и серверу?
А у меня такой вопрос к сторонникам rest-подхода — допустим вы не можете использовать http по причинам слабой производительности и есть необходимость использовать вебсокеты для браузеров (либо tcp для мобильных клиентов) — будете ли вы кодировать в передаваемых сообщениях схему http протокола и дальше в своем привычном режиме организовывать взаимодействие клиента с сервером согласно вашим представлениям rest-а или может вы решите не кодировать в сообщениях всю спецификацию http а ограничить себя каким-то подмножеством или может даже изменить какие-то моменты, или может вообще не будете смотреть на спецификацию http-протокола и закодируете в сообщениях формат общения удобный клиенту и серверу?

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

Интересно а есть движки которые предоставляют возможность создавать мир из векторов и кривых (сплайны, нурбс и т.д) и передавать этот мир на видеокарту в виде данных этих векторов/кривых а детализацию и растеризацию выполнять уже на gpu?

нет, числа не запрещать не надо, но можно запретить обращение ко всяким свойствам вроде ._proto_, .constructor и т.д, то есть составить белый список того что мы хотим разрешить — вроде базовых типов, выражений, операторов условий и циклов, и дальше в зависимости от того нужны ли другие возможности js можно также добавить конструкции и методы для создания и работы с объектами, массивами и если нужно также базовый апи Math. Date.. А инструменты статической типизации (typescript или flow) могут вообще протипизировать а мы соотвественно провалидировать еще больше фич js и если где-то встречается "any" или работа с типом который не находится в белом списке то не разрешать этому коду выполниться.
В любом случае анализ через ast это единственный надежный способ потому что все остальные способы пытаются предоставить все возможности js и убрать опасные а это очень ненадежно, на nodejs есть похожая история с попыткой сделать песочницу через vm-модуль (тут и тут) и можно только удивляться какие хаки можно придумать с этим js

можно в каждую функцию, включая анонимные добавить проверку на рекурсию (точнее проверку на таймаут). Кстати похожим образом работают инструменты покрытия кода и трассировщики — они на каждую строку или на каждый оператор добавляют инкремент счетчика и таким образом детектят что выполнилось а что нет и сколько раз, вот как в этом трассировщике например — www.youtube.com/watch?v=4vtKRE9an_I
а зачем предоставлять возможность обращаться к window? Можно предоставить только разрешенный список идентификаторов для управления персонажем (нужный набор апи) и запрещать остальные идентификаторы и тогда обращение к «window» просто не пройдет валидацию
Тут вопрос в том а нужна ли вся мощь js данном случае? Вполне возможно что для управлением персонажем достаточно будет разрешить только выражения, условия и циклы и обращение к разрешенному списку идентификаторов (апи). Тогда такое подмножество вполне успешно можно провалидировать на уровне статического анализа. Кстати насчет необходимости веб-воркеров то тоже сомнительно — даже если юзер напишет while(true) то на этапе валидации ast можно добавить модификацию — например добавить проверку времени внутри тела цикла на каждую интерацию чтобы при превышении таймаута можно было оборвать цикл.
Читая статью, складывается ощущение что попытки организовать песочницу для потенциально опасного кода через web-workers, proxy и with очень сильно смахивает на костыли. Не проще ли было просто распарсить ast-дерево js-кода и провалидировать обращение только к нужным идентификаторам?

А кто-то из знающих подскажите пожалуйста почему для рисования 3д-моделей принято использовать полигоны а не кривые? Ведь кривые позволят на порядки сократить объем передаваемой на видеокарту информации — для сплайна из десятка контрольных точек потребуется передать от сотен до тысяч точек полигона в зависимости от детализации. А ведь gpu вполне себе умеет рисовать кривые просто выполняя проверку попадания по формуле во фрагментном шейдере, без всяких там циклов (вот например статья про формулу для квадратичной кривой Безье) и по производительности получится ничем не медленней какого-нибудь стандартного освещения во фрагментном шейдере.

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

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


<div>{state.todos.map(todo=><Todo key={todo.id} todo={todo}/>)}</div>

то с веб-компонентами у которых нет биндингов (привязок данных к шаблону) вам придется писать diff вручную а это примерно в 40 раз больше строк кода


//arr - новый или измененный массив todos
//keyField - имя поля объекта для индефикации по ключу (todo.id)
//elementName - название елемента которого нужно создать если массив пополнится
//parentElement, nextElement - родительский и следующий элемент внутри и перед которым нужно сделать вставку
//this.hash - хеш где ключу соответствует нода двусвязного списка
//this.lastNode - ссылка на последнюю ноду двусвязного списка
let nextNode = null;
let oldNode = this.lastNode;
for (let i = arr.length - 1; i >= 0; i--) {
  const item = arr[i];
  const key = item[keyField];
  let node = this.hash.get(key);
  if (!node) {
    node = { element: document.createElement(elementName), key }
    this.hash.set(key, node);
    node.element.item = item;
    node.element.index = i;
    node.element.arr = arr;
    parentElement.insertBefore(node.element, nextNode ? nextNode.element : nextElement)
  } else {
    if (oldNode === node) {
      node.element.item = item;
      node.element.index = i;
      node.element.arr = arr;
    } else {
      if (node.prev) node.prev.next = node.next;
      if (node.next) node.next.prev = node.prev;
      parentElement.insertBefore(node.element, nextNode ? nextNode.element : nextElement)
    }
  }
  node.next = nextNode;
  if (nextNode) {
    nextNode.prev = node;
  } else {
    this.lastNode = node;
  }
  nextNode = node;
  if (oldNode && oldNode === node) oldNode = oldNode.prev;
}
let node = nextNode && nextNode.prev;
while (node) {
  parentElement.removeChild(node.element);
  this.hash.delete(node.key);
  node = node.prev;
}
if (nextNode) nextNode.prev = null;
Решения для синхронизации UI и состояния для фреймворков и компонентов доступны одни и те же.

Во фреймворках (react, vue, angular) это уже встроено а c вебкомпонентами либо придется писать код в стиле jquery вручную меняя дом-элементы либо использовать дополнительно еще фреймворк/шаблонизатор который будет менять ui в зависимости от изменения данных но тогда смысл в веб-компонентах как в самодостаточной технологии теряется.
В чем фундаментальность этого недостатка, можете привести юзкейсы, когда это надо?

Ну как же — без поддержки svg веб-компоненты это какая-то обрезанная и ограниченная версия компонентой модели. Если какой-нибудь html-шаблон можно разбить на компоненты то чем svg хуже? Как с веб-компонентами вы планируете строить какой-нибудь сложный интерфейс на svg например онлайн-фотошоп или приложение для создание презентаций где будут графики, кривые безье, сложные фигуры и т.д?
На эту тему можно долго рассуждать, вспомнить JSX, VDOM и прочее, но для нас сейчас главным является вопрос: а альтернатива то какая? Нет, не Vue. И не, тем более, Angular. Для меня это веб-компоненты: набор стандартов CustomElements + ShadowDOM + ES Modules + HTML Templates. Почему? Потому, что это стандарты, поддерживаемые самой веб-платформой, а не мета-платформы и JS-надстройки..

Забавно, раньше под статьей про реакт или про какой-то другой js-фреймворк кто-то непонимающий обязательно напишет «А зачем оно надо, я на жуквери напишу за 15 минут» а теперь похоже настало время фраз — «зачем мне фреймворки если есть веб-компоненты»
Да, вебкомпоненты позволяют разбить на компоненты, изолировать стили и прочее, но они не никак решают самую главную проблему существования js-фреймвоков — проблему синхронизации ui и состояния. На эту тему даже есть отличная статья — medium.com/dailyjs/the-deepest-reason-why-modern-javascript-frameworks-exist-933b86ebc445.
Ну и вдобавок веб-компоненты имеют один фундаментальный недостаток — они не поддерживают svg. Вот захотите вы разбить какой-то svg-шаблон на компоненты также как и html, а нет — нельзя, приплыли. Когда же в реакте да и в остальных популярных js-фреймворках можно свободно разбивать svg на компоненты.

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


пример однопроходного парсера
const Code = '(substract (add 4 21) (mul 2 2))';

let Position = 0;
function parseExpr() {
    let number;
    if (parseSpace() && (number = parseNumber()) && parseSpace()) {
        return number;
    }
    let callExpr;
    if(callExpr = parseCallExpr()){
        return callExpr;
    }
}
function parseCallExpr(){
    let name;
    let args;
    if (parseSpace() && parseToken('(') && parseSpace() && (name = parseName()) && parseSpace() && (args = parseArguments()) && parseSpace() && parseToken(')') && parseSpace()) {
        return {
            type: 'CallExpression',
            name,
            args
        };
    }
    return null;
}

function parseArguments() {
    let expr;
    let args;
    if ((expr = parseExpr()) && ((args = parseArguments()) || true)) {
        return [expr, ...(args || [])];
    }
    return null;
}

function parseSpace(){
    let char = Code[Position];
    while (/ /.test(char)) char = Code[++Position];
    return true;
}

function parseNumber() {
    let current = Position
    let char = Code[current];
    var value = "";
    while (/[0-9]/.test(char)) {
        value += char;
        char = Code[++current];
    }
    if (value.length === 0) {
        return null;
    } else {
        Position = current;
        return {
            type: "NumberLiteral",
            value: value
        };
    }
}
function parseName() {
    let current = Position
    let char = Code[current];
    var value = "";
    while (/[a-zA-Z]/.test(char)) {
        value += char;
        char = Code[++current];
    }
    if (value.length === 0) {
        return null;
    } else {
        Position = current;
        return {
            type: 'Identifier',
            value: value
        };
    }
}

function parseToken(token) {
    let current = Position;
    let char = Code[current];

    let i = 0;
    char = token[i];
    while (Code[current] === char && i < token.length) {
        i++;
        current++;
        char = token[i];
    }

    if (i !== token.length) {
        return false;
    } else {
        Position = current;
        return true;
    }
}

console.log(JSON.stringify(parseExpr()));

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

Information

Rating
Does not participate
Registered
Activity