Как стать автором
Обновить

Комментарии 26

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

Потому что зашивание чего либо в код является плохой практикой. Так же само как и "меджик намберс" и другие вещи. По идее, такие вещи, как версия, если ее интегрировать внутрь приложения, должны храниться в .env файле, который находится в гит игноре. А значит у вас будет деплой не только автоматически кода из ветки, но и ручками придется править конфиг на сервере. А во вторых, фронт должен знать, что не только он изменился, но и бэкенд тоже. А значит нужно сохранять именно две версии, и фронта и бэка. А вот при деплое бэка во фронт вы уже ничего на проде не зашьете. Простой пример, допустим в фронт зашита версия фронта №1. На бэке для фронта такая же версия. Все хорошо. Потом мы деплоим бэкенд, где полностью меняются АПИ для формы создания контента для залогиненных пользователей. Бэкенд изменил свою версию, а вот фронт все тот же с версией №1, что не даст перегрузить приложение в браузере. И при попытке заполнения формы контента, пользователь получит серверную ошибку, что такого пути не существует. Именно по этому и нужно проверять две версии, и фронтенда и бэкенда. Плюс версионность вообще не нужна для тех, кто просто зашел на сайт контент почитать. Они пришли и ушли. А вот те, кто работает с приложением под логином, у тех оно часто неделями открыто в браузере, и они активно работают с приложением. И вот для этого как раз и нужна перезагрузка.

Как в вашем "простом примере" перезагрузка неизменившегося фронта поможет решить проблему совместимости с изменившейся версией бэка?

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

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

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

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

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

Кроме того не нужно везде использовать trim (и при записи, и при чтении, и при проверке), и явно приводить ради этого версию к строке, и использовать optional chaining на переменной, которая предварительно проверяется на тип 'object' (вас что линтер не предупреждает о таких вещах?).
Проверять версию нужно ровно так как она записана при сборке, иначе вы рискуете получить рассинхрон при проверке на фронте и бэке и будете вечно перезагружать фронт, потому что "1.1" != "1.1 "

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

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

Я не мастер писать статьи, но если вам действительно интересно, то опишу базовую идею. Чтобы убедиться, что код, работающий в браузере отличается от кода, который есть на сервере, нужно сравнивать какое-то значение полученное с сервера со значением, сгенерированным в работающем коде. Причем это сгенерированное значение должно зависеть только от загруженного кода, а не от внешних переменных, хранимых в localStorage или других хранилищах. Надежным примером такого значения может быть хэш от кода. Например сервер может отдавать хэш от последней сборки, а код на фронте может сам посчитать этот хэш от загруженных ресурсов и сравнить с полученным от сервера. Но это сложный вариант. Для упрощения можно обойтись некоторой константой, которая опять же при сборке приложения автоматически вставляется в код и она же записывается в соседний файл, который будет отдавать сервер. Тогда достаточно сравнить значение константы в коде и значение, полученное от сервера. В качестве значения константы можно использовать номер комита, как в вашем случае. Я, например, просто использую значение version из package.json, его же показываю пользователю где-нибудь в разделе about. Его же шлю в заголовках всех API запросов, чтобы сервер мог подстроиться и отдать мне совместимый с данной версией ответ, либо ругнуться, как в вашем случае. Дополнительно, превентивно запрашиваю версию с сервера когда пользователь переключается на вкладку с моим приложением (если с момента последнего переключения прошло достаточное количество времени), ну и по таймеру еще могу проверять, если для приложения очень важно быстро обновляться. Как-то так...

Спасибо за описание вашей реализации и ваше видение. А вот вопрос, если зашивать версию фронта в код, то теоретически может получиться такая ситуация, что произойдет ошибка, и в константу версии на фронте запишится не то, что было нужно. И получится расинхрон между фронтом и бэком. Но исправить его перезагрузкой фронта уже не выйдет, и фронт уйдет в бесконечную перезагрузку. Как с этим быть? Если хранить версию в package.json, то это нужно ручками добавлять? Если ручками, то это точка возможного человеческого фактора, можно просто забыть. Если автоматически, то тогда нужно после этого не забыть запушиться в репу. По поводу версии самого софта, у меня тоже есть решение, о котором вы говорите, и прямо с секцией about тоже )) Вот картинка.

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

сначала попробую ответить на ваши вопросы:

рассинхрон между фронтом и бэком невозможен даже теоретически, потому что в этой схеме бэк вообще не участвует. Есть папка на сервере, из которой nginx раздает статику. Один из таких статических ресурсов - файл version, который является частью дистрибутива формируемого скриптом сборки. Поскольку скрипт записывает одно и то же значение и в константу в исходниках и в файл version, то это гарантирует, что значения не будут отличаться, если сборка прошла успешно. Аналогично, обновлением ресурсов также занимается скрипт деплоймента, он гарантирует, что если деплоймент прошел успешно, то в файлы в папке на сервере будут ровно те же, что выдал скрипт сборки. Права на запись есть только у скрипта деплоймента. Nginx эти файлы только читает. Файл version в процессе деплоймента записывается последним. Теоретически возможна ситуация, когда клиент пришел в момент, когда задеплоились файлы приложения, а файл version еще деплоиться, но вероятность того, что он успеет загрузить весь фронт, проинициализироваться, запросить version, а тот еще не успел обновиться мы оцениваем как крайне низкую и в худшем случае клиент перезагрузится еще раз. Для параноиков я уже упоминал вариант с хэшом от файлов дистрибутива. В качестве еще одной альтернативы вместо файла version можно запрашивать index.html, как правило он небольшой и если у вас имена ресурсов содержат хэши, то можно просто сравнить, что имена ресурсов работающего приложения совпадают с именами в index.html, полученном с сервера.

Да, версию в package.json нужно обновлять ручками. Я остановился на этом варианте, потому что предпочитаю иметь контроль над тем когда нужно принудительно обновить код на клиенте, а когда это можно пропустить (я не хочу заставлять клиента обновлять код приложения, если комит не меняет поведение, например комментарии в коде поправили). Плюс я получаю единый источник правды и для проверки и для отображения пользователю (в вашей версии текст показываемый пользователю в диалоге about не похож на номер комита, записываемый в файл, вы его где берете, как обеспечиваете согласованность?) Для тех, кто забывает править версию или боится накосячить с ручной правкой, как я упоминал ранее, можно использовать автоматический номер комита или номер сборки CI или прекомит-хуки.

Пассаж про ваш механизм апдейта мне не понятен. Статья называется "Обновление SPA приложения в браузере пользователя..." в какой момент появились пользователи, которым нужно обновить бэк у себя на сервере и при чем здесь white-label продуктов?

Смотрите, вы так стараетесь мне донести свою позицию, что не пытаетесь понять о чем я говорю. Статья написана именно о том, как после деплоя перегрузить фронт в браузере. Это одно законченное повествование с примерами. Второй момент, об about о котором я упомянул в комментарии выше - это совсем другой кейс, который вообще с этим не связан. Представьте, что вы компания/команда/бизнесмен, кто продает кастомный лицензионный софт. И у этого софта есть релизные версии. И вот пользователь вашего лицензионного софта (другая компания), хочет проверить, нет ли новой версии (это и фронтенд и бэкенд часть на их персональном сервере). И если есть новая версия релиза, то нужно его загрузить на свой сервер и обновить старые версии софта. И этот кейс вообще никак со статьей не связан. И работает совсем в другой логике и парадигме, намного сложнее, чем я описал в статье. Это можно рассматривать, очень условно, как обновлении линукс или виндовс. Спасибо, что вы поделились своим видением и подходом к реализации. Любое решение имеет право на жизнь. А какое именно решение подходит в том или ином случае, то это уже пусть выбирают архитектор/техлид/команда. Чем больше вариантов, тем лучше. Спасибо вам за диалог.

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

Одним из главных недостатков вашего решения я вижу как раз использование localStorage. Значение в нем никак не коррелирует с кодом, работающим в браузере. Для иллюстрации предлагаю рассмотреть не гипотетическую, а вполне конкретную ситуацию. Откройте свое приложение в двух вкладках браузера, выполните деплой новой версии, начните работать в первой вкладке, дождитесь сообщения о новой версии и обновления страницы, перейдите во вторую вкладку, ответьте на вопросы: какая версия кода работает во вкладке? Какую версию шлет в заголовках код, работающий в этой вкладке? Или немного менее распространенный, но все же имеющий место быть пример - что будет если ваше приложение откроется в Webview (например по ссылке в мессенджере), в котором localStorage часто недоступен?

Плюс некоторая неаккуратность в коде наводит на мысль о том, что решение сделано в спешке, без глубокой проработки и поэтому переусложнено. У вас версия это строка или число? Почему при наличии файлов version.back и version.front значение в полях возвращается строковое, а в случае отсутствия числовое? Зачем нужны эти многочисленные приведения к строке и тримы? Почему при наличии только одного файла version.back или version.front и отсутствии другого сервер вернет неверное значение (что обе версии нулевые)? Зачем при этом физически создавать дефолтные файлы на диске, что это дает? В реальной работе приложения конечно все эти мелочи проблем не создадут и в конечном итоге приведут к нужному результату, но весь этот лишний код затрудняет изучение другими разработчиками, заставляя их задаваться лишними вопросами, а как известно из фольклора, качество кода измеряется в количестве WTF в единицу времени :)

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

Откройте свое приложение в двух вкладках браузера, выполните деплой новой версии, начните работать в первой вкладке, дождитесь сообщения о новой версии и обновления страницы, перейдите во вторую вкладку, ответьте на вопросы: какая версия кода работает во вкладке?

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

Зачем завязывать обновление фронта на версию бэка?

Для того, чтобы не ломать себе голову, почему может что-то сломаться. Проще обновить страницу и не парить себе мозги. Может быть много разных причин, как изменения бэка могут повлиять на то, что в браузере старая инициализация. А вот что именно, это не существенно. Деплой был, версия изменена, хоть бэка, хоть фронта, значит уведомляем пользователя и обновляем страницу. Это моя философия, если хотите. Лучше так перестаховаться, чем у клиентов возникнут возможные баги в браузере, о которых вы даже и не узнаете никогда.

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

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

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

У меня очень простая философия - код, это код, а конфиги, это конфиги. И прямой связи между этими вещами быть не должно. Связь должна быть только абстрактной, а не прямой. Это позволяет разделять разные сущности и легко ими управлять, даже в реальном времени. А еще, данные на бэкенде имеют отношения только к бэкенду, поэтому всякие динамические словари тоже во фронт я не зашиваю, а получаю динамически с фронта и тд. Короче говоря, отделяю зерна от плевел. Именно по этому, фронтенд и не должен ничего знать о том, что он там за версия, это должен определять сервер, а не код на фронте. По факту, это вообще условность и не более того, так как цель не в версии, а в четком понимании того, что нужно перегрузить страницу. Таким образом, фича версионности может вообще отдельно работать и от бэкенда и фронтенда. Именно для этого и нужны файлы с версиями. Это тоже абстракция, без жесткой привязке к коду. Сегодня я так создаю номера версий, а завтра по другому, вообще сторонним софтом. И ничего менять при этом в коде не потребуется. И локальное хранилище - это тоже абстракция для временного хранения версии, которую вы получите только тогда когда залогинитесь. Таким образом, если вы захотите, то прямо из своей админки супер админом сможете поменять версию в файле версии, тем самым заставив всех залогиненных пользователей автоматически перезагрузить страницу. Это гибкость, а не деревянная реализация, прибитая гвоздями.

Или немного менее распространенный, но все же имеющий место быть пример - что будет если ваше приложение откроется в Webview (например по ссылке в мессенджере), в котором localStorage часто недоступен?

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

У вас версия это строка или число?

А когда вы данные из файла читаете, то какой это тип данных?

Почему при наличии файлов version.back и version.front значение в полях возвращается строковое, а в случае отсутствия числовое?

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

Зачем нужны эти многочисленные приведения к строке и тримы?

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

Почему при наличии только одного файла version.back или version.front и отсутствии другого сервер вернет неверное значение (что обе версии нулевые)? Зачем при этом физически создавать дефолтные файлы на диске, что это дает?

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

но весь этот лишний код затрудняет изучение другими разработчиками, заставляя их задаваться лишними вопросами, а как известно из фольклора, качество кода измеряется в количестве WTF в единицу времени :)

Нужно не код изучать, а главную идею. Именно поэтому я так много пояснений даю. А во вторых, тут ничего лишнего в коде нет. Он примитивен, по факту, а его количество - это пару десятков строк. Куда уже проще? Но при этом у вас полная независимость и бэкенда и фронтенда от самого кода. Больше ничего руками делать не нужно. А управление версиями можно положить на что угодно, просто записывая их в файлы версий на сервере, и все будет работать автоматически.

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

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

Надеюсь, я смог прояснить некоторые моменты реализации.

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

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

Мне посчастливилось с этим столкнуться. Видимо на том проекте огромный трафик. Rolling update deployment'a занимает некоторое время и в течение нескольких минут запросы на домен сайта могут случайным о разом попадать как в новые поды, так и в старые.

Для решения проблемы сделал так:

  • Создал persistent volume для хранения ассетов, причём внутри делаю две папки - old и new

  • Перед обновлением подов, удаляю содержимое папки old, копирую все из new в old, удаляю все из new, копирую ассеты из нового образа в new

  • Перед приложением стоит nginx, который раздаёт файлы ассетов хитрым образом: сначала файл ищется в образе, если там нет, то в папке new, а если и там нет - то в папке old

Таким о разом получается не важно какая версия клиента в какой pod послала запрос - нужный файл так или иначе будет найден.

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

А если, теоретически, использовать всегда папки new и old вместе. Nginx всегда отдает из новой, если не находит, то из старой. А при деплое переименовывать новую в старую, а в новую помещать новый билд. Как бы всегда два последних билда будут вместе, до следующего деплоя.

Что-то я затупил. Вы же так и делаете ))

Учитываете ли вы введенные данные на формах? Например, пользователь заполнял форму и дошел до "сложного" компонента, который загружает данные по отдельному API. И если выяснится, что после выполнения запроса версия фронта устарела, то потребуется обновить страницу. Что будет с введенными данными на форме? Или у вас всё предусмотрено или в принципе нет таких кейсов?

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

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

Тут может возникнуть другая проблема. Например, пользователь начал заполнять форму, а потом передумал и решил закрыть страницу. А потом он возвращается на сайт, открывает форму, а ему происходит автоподгрузка из черновика. Тогда нужно делать дополнительную кнопку сбросить всю форму (а если она многошаговая). Или значения в словаре с бэка поменялось, а сохранилось в хранилище то, которого уже не существует. Пользователя может начнет бомбить от такого поведения формы. Я хочу сказать, что нет идеальной архитектуры и решений. Это всегда компромиссы между чем-то. Именно поэтому и нужно очень глубоко анализировать потребности бизнеса и продукта, чтобы эти компромиссы были максимально оптимальными для конкретных случаев. Для этого и нужны солюшен архитекторы, чтобы и создавать эти оптимальные решения под конкретные задачи. А еще, чем проще решение, тем лучше. И нет таких понятий, как правильно или неправильно. Это субъективщина. Есть то, что работает, и то, что не работает. А вот среди того, что работает, тоже можно рассматривать те решения, которые проще и эффективнее. Все эти концепции выходят далеко за область знаний и компетенций разработчиков. По факту - это уже про системный анализ и методологию. Именно поэтому, почти все мои статьи не узко технического плана, а о процессах, мышлении, управлении и архитектуре. Самое главное - это не умение писать код, а мышление. И вы, как раз, и задаете правильные вопросы, эмитируете возможные ситуации и способы их решения. Это все можно еще дальше развить. Нарисовать алгоритмические блок схемы со всеми возможными вариантами, которые сейчас видны. Когда это будет нарисовано, появятся и не очевидные моменты. И вот когда будет перед глазами полный воркфлоу, тогда и можно будет увидеть и проанализировать самый оптимальный вариант реализации, естественно со всеми плюсами и минусами. Найти тот необходимый компромисс именно для конкретной задачи.

Мы сейчас размышляем над задачей, которая точно не сформулирована и всех нюансов не учесть.

Даже если всё-таки форма многошаговая, то нет ничего отталкивающего с точки зрения UX в кнопке "сброс". Если значение в словаре на бэке удалили (заархивировали), а на клиенте оно есть - тоже не вижу проблем сделать соответствующие проверки и очищать поле. Далее, можно придумывать требования какие угодно - смена набора полей для формы, внедрение версионирования данных и прочее...

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

Даже если всё-таки форма многошаговая, то нет ничего отталкивающего с точки зрения UX в кнопке "сброс"

Вот представьте, у вас есть степпер на 5 шагов. Каждый шаг - это новая страница с формой, которая зависит от выбора в предыдущих шагах. Вы на каждой странице будете размещать кнопку сброса? А что должно произойти после нажатия на кнопку сброс, когда пользователь, к примеру, на третьем шаге? Его нужно редиректнуть? Если да, то куда? А если не редиректить, то после очистки формы (предыдущих шагов) он получит ошибку формы, так как она зависимая и в нее не подгрузило данных. Видите, четко сформулированной задачи так и не появилось, но вот варианты последствий можно и без этого увидеть. Вот вам и поле для размышлений и компромиссов. Но я дам подсказку. Обычно форму сохраняют для тех пользователей, кто пытался пройти через воронку продаж, но так и не завершил свои действия. И не в локальном хранилище, а в БД. И не для удобства пользователя, а для маркетингового анализа. Последний кейс, что я вам описал, выходит уже за зону ответственности программиста.

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

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

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

Ага, все об этом говорят, но никто этого не видел, а тем более сам не делал. А так да, все знают как нужно, но все время что-то мешает это сделать. Я об этом писал в своей предыдущей статье: "Игнорирование стратегии приводит к проблемам на проекте"

Про многошаговасть - какое-то усложнение. Например, при установке какого-то софта на свой ПК вы часто видели, чтобы "сброс" редиректил куда-то на шаг назад. На то он и сброс, чтобы очистить состояние (считайте "отмена"). А для предыдущего шага - кнопка "назад".

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

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

Не вижу смысла продолжать дискуссию, все высказались и остались при своем мнении. За статью спасибо :)

А как же те сотни докладов на конференциях "как мы все переписали и стали жить лучше"?

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

У меня всего два вопроса:

  • умные люди 40 лет назад реализовали т. н. hot upgrade в высоконагруженных серверных системах, на которых до сих пор работает ¾ роутеров в интернете, почему бы не почитать, как они это сделали, и не перенять некоторое количество здравых идей оттуда?

  • как в тексте про SPA в 2025 году могут быть не упомянуты конкуренты типа LiveView и его аналоги в других языках типа HotWire и компании?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации