В конце лета 2015 года я оставил свою работу в качестве руководителя ИТ-отдела и полностью посвятил себя разработке приложений. Я использовал Node & React в небольших проектах, но хотел посмотреть, как это будет работать в больших приложениях, и понять, как себя чувствует сквозное приложение JavaScript. Поскольку мои навыки дизайнера в лучшем случае ограничены, мне повезло работать с Себастьяном, чей опыт в данной области был неоценим.

Выбор стека

Как разработчик я получил полную свободу выбора стека, который мы будем использовать в приложении, и всю связанную с этим ответственность, если что-то пойдет не так. Я выбрал комбинацию React для уровня представления, Webpack в качестве сборщика, Socket.Io для связи в реальном времени, Ramda в качестве служебной библиотеки, Koa в качестве веб-фреймворка и RethinkDB в качестве базы данных. Следуя правилу, согласно которому в каждом проекте должно быть не более одной передовой технологии, я решил, что это будет RethinkDB. Остальной стек — это опробованные и проверенные технологии, которые я уже использовал в предыдущих проектах помимо Koa. Поскольку у меня большой опыт работы с библиотеками TJ, я был уверен, что они будут работать отлично.

Организация стека

Хотя все технологии чувствовали себя прекрасно, заставить их хорошо играть оказалось довольно интересным занятием. Сначала я планировал использовать либо Redux, либо Cerebral, но оба были очень новыми, не хватало ресурсов для обучения, и у меня было ощущение, что Дэн и Кристиан меняют их каждую неделю. Уже выбрав одну передовую технологию, я решил написать свою диспетчерскую.

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

function (data,dispatch,model,transfer){ 
   return model.setIn([‘filter’,’distance’], data);
}

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

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

<Hero {…(U.props(Hero.propTypes, this.props))} />

Рендеринг на стороне сервера

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

Renderer.render = function(model){ 
  var startState= JSON.stringify(model.asObject()); 
  var body = React.renderToString(App({model: model})); 
   var html = React.renderToStaticMarkup(Layout({
       body : body,   
       startState : startState 
   })); 
   return ‘<!DOCTYPE html>’+html;
}

Тот же подход к рендерингу использовался для рендеринга электронных писем в формате HTML. Рендеринг на стороне сервера React устраняет необходимость в отдельной системе шаблонов.

Связь в режиме реального времени

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

Я по-прежнему считаю, что RethinkDB — отличная база данных, но без соединений в реальном времени ее использование весьма ограничено.

Хостинг

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

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

Безопасность

Настоящая головная боль случилась перед пре-запуском, когда мы разместили свой сайт и обратились за советом к сообществу белых шляп. Нас тут же забили XSS-атаками. Отсутствие безопасности типов в JavaScript и моя неопытность в защите приложений, размещенных за пределами защищенных интрасетей, быстро проявились. К счастью, я планировал скучную консервативную проверку кода как на стороне клиента, так и на стороне сервера, поэтому я исправил формы и файлы cookie. Самой большой проблемой была сериализация состояния приложения. К своему ужасу, я обнаружил, что рендеринг результата JSON.stringify не дает читабельного состояния на клиенте. Мне пришлось дезинфицировать мою модель и поместить ее в тег скрипта как шаблон/текст.

Тестирование

Сочетание чисто функциональных компонентов и тестирования с помощью утилит тестирования mocha, chai и React сделало тестирование относительно простым и быстрым. Проблема с нашей настройкой заключалась в том, что она не была интегрирована с процессом фиксации, что позволяло обманывать, фиксируя код, который не прошел тесты. Вторая проблема больше связана с отсутствием типов в JavaScript. Поначалу мне нравится динамическая типизация, поскольку она позволяет мне экспериментировать, пока проблема не определена, но позже, когда у меня появилось представление о том, чего я хочу, это становится бременем. Рефакторинг динамически типизированного кода всегда вызывает у меня щемящее чувство, что я что-то упустил, даже когда все тесты проходят. Это очень тревожное чувство, которое никогда не проходит.

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