Разработка с Meteor.js и реактивное программирование

в 14:41, , рубрики: javascript, Meteor.JS, node.js

Я начал работать с Meteor.js примерно полтора года назад, когда он был еще совсем сырым и нестабильным. Я сделал пару маленьких проектов, которые живы до сих пор, написал статью на Хабру и даже получил дикие проблемы в продакшене с забиванием CPU на 100% при том, что сервер почти ничего не делал. Но прошло время, и я решил поставить еще один эксперимент и разработать с использованием этого фреймворка проект средней сложности. Разработка затянулась, и в процессе я сформулировал свои правила того, как структурировать приложение, как решать проблемы с безопасностью, как деплоить и какие инструменты использовать на серверах. Об этом и расскажу.

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

Разработка с Meteor.js и реактивное программирование

Реактивность

Если следите за трендами в мире программирования, вы точно много раз слышали этот термин. Скорее всего, вы слышали его настолько часто, что он встал в один ряд с «big data». Как и в случае с «big data», термин «реактивность» употребляют теперь по поводу и без. Если вы не уверены, что знаете точно, что же такое «reactive programming», советую почитать небольшую статью The Reactive Manifesto, а тем, кто окончательно угорел по реактивному программированию и хочет еще, — пройдите курс c Мартином Одерски.

Meteor.js изначально позиционировался как фрэймворк, основанный на парадигме реактивного программирования, поэтому, если вы не прочитали ссылку выше, просто запомните 4 понятия: масштабируемость (scalability), событийно-ориентированное программирование (event-driven programming), отзывчивость (responsivness) и устойчивость (resilience). Они понадобятся нам позже.

С чего начать

Начните с чтения документации.Затем узнайте про meteorite и atmoshpereблоге приложения Kadira можно найти много интересных статей про Meteor.js. Само приложение посвящено мониторингу Meteor приложений (типа newrelic).Посмотрите скринкасты от разработчиков Meteor.js

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

Структура приложения

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

  • client/helpers
  • client/vendor
  • client/views
  • controllers
  • methods
  • models
  • repositories
  • server
  • services
  • templates

Общая структура такая:
Для каждой страницы (роута) создается controller. У каждого controller есть один или несколько template. У каждого template есть один view.

Теперь подробно рассмотрим каждую директорию и то, что там должно быть.

Controllers

В этой папке лежат контроллеры. Я предлагаю использовать IronRouter. Он сделан специально для Meteor, поддерживает определение роутов на клиенте и сервере, создание контроллеров и layout'ов. Он достаточно популярен, и к нему есть приличное количество плагинов (например, iron-router-progress).

Models

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

Repositories

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

Server

Здесь хранятся все файлы, которые должны исполняться только на сервере.

Services

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

Templates

Здесь лежат html-файлы.

Client

Все файлы, которые здесь лежат, будут загружены только на клиенте.

Views

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

Пример:

Template.dbase.events =
  'click .js-more': ->
    @questionPagination.loadNextPage() if @questionPagination.ready()

Template.friends_guess.showSteps = ->
  StepsHelper.showSteps(@questionNum, @questionMax)

Helpers

Клиентский код, который используется в нескольких разных view.

Vendor

Клиентский библиотеки, которые вы подключаете для проекта.

Methods

И наконец, тут лежат определения Meteor-методов

Ограничение доступа к базе

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

С базовыми принципами можно ознакомиться здесь.Тем не менее, остается вопрос. Как организовать код, который будет определять: разрешить или нет запрошенную операцию. Для примитивных случаев можно просто описать все в callback при вызове .allow или .deny, но для более сложного поведения это не подходит. Нам нужна какая-то форма, которая бы позволяла композицию, переиспользование и понятную организацию кода.

Говоря о безопасности, принято считать, что лучше организовывать защиту как набор независимых слоев. Исходя из всего вышеперечисленного, я решил выбрать структуру аналогично потокам из node.js:

class @Guard
  @create: ->
    new @

constructor: ->
  @_heads = []

pipe: (head) ->
  @_heads.push head
  @

guard: ->
  self = @
  ->
    args = arguments
    ctx = @
    _.all(self._heads, (head) ->; head.apply(ctx, args))

Пример использования:

Answer.allow
  insert: Guard.create().
    pipe(RegisteredHead).
    pipe(GameNotEndedHead).
    guard()

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

Развертка и поддержка приложения

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

Но для начала вам нужно настроить сервера и выбрать web-сервер, если вы не хотите, чтобы node.js раздавал статику (вы точно не хотите). Самый простой вариант — nginx. Вам не составит никакого труда настроить nginx раздавать статику и апстримить запросы вашему приложению. Но в этой ситуации вам придется думать о супервизоре, о том, как организовать кластеризацию (распараллеливании node.js приложения).

Лично я вам советую использовать Phusion Passenger. Уже примерно полгода он умеет работать как с node.js, так и конкретно с Meteor приложениями. Самым оптимальным будет установить его как плагин для nginx, а также у них есть целая статья про то, как настроить passenger работать в связке с Meteor.

Также Meteor умеет читать Mongo Oplog для того, чтобы отслеживать изменения в базе. Это довольно сильно уменьшает нагрузку на CPU, но, чтобы это заработало, нужно настроить инстанс Mongo как Replica Set.

Из коробки Meteor поддерживает команду meteor deploy. Эта команда отправит ваш код на хостинг, который разрабатывают создатели Meteor специально для хостинга приложений этого фреймворка. Это очень удобно для того, чтобы просто потестировать приложение или организовать staging environment, но для production логичнее иметь свои собственные сервера.

Способов организовать деплой на свои собственные сервера много, но мне больше всего нравится node-модуль flightplan, поэтому для него у меня уже написан специальный рецепт, который заточен под интеграцию с Phusion Passenger и умеет делать zero-downtime deployment (более или менее).

Полезные smart packages

Здесь я просто перечислю список полезных smart packages, которые помогли мне при разработке. Все их можно найти на Atmosphere.

  1. accounts-vkontakte — добавляет авторизацию для VK в стандартный модуль авторизации Meteor
  2. iron-router — лучший router для Meteor приложений
  3. user-status — сообщает о том, находится ли сейчас конкретный пользователь на online.
  4. define — если у вас много файлов, то вам необходима система модулей. Эта очень простая, но есть все, что нужно
  5. houston — хорошая админка
  6. cron-tick — мини-cron внутри Meteor приложения
  7. kadira — модуль мониторинга приложения для сервиса Kadira

Реактивность

Теперь поговорим про то, насколько Meteor удовлетворяет свойствам реактивного приложения.

Event-driven development

Тут обсуждать нечего: конечно, удовлетворяет, правда, в немного извращенном варианте. Вместо подхода, который предлагает, например, Rx или Observables в Scala, где мы оперируем потоками событий, здесь вся событийность от нас спрятана. Мы работаем с простым сиинзронным js-кодом. Это, с одной стороны, проще, с другой — сильно усложняет понимание того, как все работает внутри.

Scalability

Так как Meteor — обычное node приложение, то и вопрос scalability решается для него точно так же. То есть сравнительно неплохо. Если вы используете Phusion Passenger, то он позаботиться о том, чтобы держать нужное количество инстансов приложения. Можно долго спорить о быстродействии MongoDB, но она довольно легко масштабируется, так что выбор именно этой БД добавляет приличное количество очков к параметру масштабируемости Meteor приложений.Если вы хотите знать больше о том, как себе все это представляют создатели Meteor, посмотрите это видео.

Resilience

Ну тут все опять же упирается в архитектуру node.js. А мы знаем, что node.js работает в одном треде и, несмотря на высокую пропускную способность, в определенный момент начинает захлебываться. За что его многократно пинали ногами любители Erlang в многочисленных бенчмарк-тестах. Также, если node.js приложение падает, то падает дружно всем процессом, и ничего кроме перезапуска тут не поможет. Так что я бы не назвал Meteor приложением устойчивыми.

Responsive

Это главная фишка Meteor. Бесплатный real-time из коробки.

Итог

В общем и целом, Meteor приложения можно назвать реактивными с некоторыми оговорками. Основные ограничения тут, скорее, не у Meteor, а у node.js. Но, так или иначе, Meteor точно может претендовать на реактивность.

Преимущества

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

Скорость разработки

Разрабатывать под Meteor действительно приятно. Livereload из коробки, встроенная система билдинга статики. Удобство добавления препроцессоров. Подключение новых файлов на лету. Деплой встроенной командой на сервера *.meteor.com для тестирования. Здесь все сделано, чтобы вы тратили меньше времени на разработку.

Spacebars

Spacebars — переписанный handlebars. Чем-то напоминает Reac.js и htmlbars, но (по ощущениям) работает медленнее, а также обладает магической способностью не менять html, который вы поменяли с помощью jquery (например), меняя все вокруг него. При этом не надо оборачивать в шаблоне этот кусок кода ни в какие хелперы. В общем, если вы хотите изменить часть страницы так, чтобы state приложения не менялся, spacebars это может.

Гомогенность

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

Проблемы

Несмотря на все преимущества, есть и недостатки.

Нестабильность

Что ни говори, а текущая версия Meteor — 0.8. Когда будет 1.0 непонятно, а значит будут изменения API, баги и регрессии. Но в принципе, с этим можно жить. Также я несколько раз сталкивался с багом, когда Meteor просто съедал 100% CPU без видимых на то причин, но в последних версиях этого, вроде бы, уже нет.

Отладка

Когда я говорил, что отзывчивость (responsiveness) реализована немного извращенно, я не шутил. Часть логики обновления страницы при появлении новых данных реализуется с помощью исключений (exceptions), и это влечет неприятные последствия: иногда вы просто не видите ошибку, потому что она была поймана каким-то внутренним обработчиком Meteor, и тут приходится заниматься отладкой вслепую, это действительно бесит.

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

Неопределенность

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

Заключение

Создатели Meteor называют своей главной целью максимально упростить разработку web-приложений. Сделать процесс создания прототипа максимально быстрым и удобным. И мне кажется, что Meteor действительно сильно упрощает и ускоряет разработку. В нем довольно много новых и интересных идей и это, пожалуй, первый проект, который использует преимущества разработки frontend и backend на одном языке.Если вы думаете, стоит ли использовать Meteor в своем следующем проекте, то задайте себе три вопроса:

  1. Нужен ли вам real-time?
  2. Подходит ли вам MongoDB?
  3. Любите ли вы bleeding edge технологии?

Если хотя бы на два вопроса вы ответили «да», то советую дать Meteor шанс.

Собственно сам проект, который я реализовал, можно посмотреть здесь: ilito.paperpaper.ru. Мне понравилось, и я непременно буду использовать Meteor в дальнейшем.
Если у вас есть какие-то вопросы, пишите в комментариях или мне в твиттер @thought_sync.

Автор: Terminal

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js