Я начал работать с Meteor.js примерно полтора года назад, когда он был еще совсем сырым и нестабильным. Я сделал пару маленьких проектов, которые живы до сих пор, написал статью на Хабру и даже получил дикие проблемы в продакшене с забиванием CPU на 100% при том, что сервер почти ничего не делал. Но прошло время, и я решил поставить еще один эксперимент и разработать с использованием этого фреймворка проект средней сложности. Разработка затянулась, и в процессе я сформулировал свои правила того, как структурировать приложение, как решать проблемы с безопасностью, как деплоить и какие инструменты использовать на серверах. Об этом и расскажу.
Внимательный читатель уже заметил, что я ничего не сказал про автоматизированное тестирование. Да, я не писал тесты, и это плохо. Как говорят в таких случаях: было мало времени. Если хотите почитать про тестирование 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. Эта команда отправит ваш код на
Способов организовать деплой на свои собственные сервера много, но мне больше всего нравится node-модуль flightplan, поэтому для него у меня уже написан специальный рецепт, который заточен под интеграцию с Phusion Passenger и умеет делать zero-downtime deployment (более или менее).
Полезные smart packages
Здесь я просто перечислю список полезных smart packages, которые помогли мне при разработке. Все их можно найти на Atmosphere.
- accounts-vkontakte — добавляет авторизацию для VK в стандартный модуль авторизации Meteor
- iron-router — лучший router для Meteor приложений
- user-status — сообщает о том, находится ли сейчас конкретный пользователь на online.
- define — если у вас много файлов, то вам необходима система модулей. Эта очень простая, но есть все, что нужно
- houston — хорошая админка
- cron-tick — мини-cron внутри Meteor приложения
- 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 в своем следующем проекте, то задайте себе три вопроса:
- Нужен ли вам real-time?
- Подходит ли вам MongoDB?
- Любите ли вы bleeding edge технологии?
Если хотя бы на два вопроса вы ответили «да», то советую дать Meteor шанс.
Собственно сам проект, который я реализовал, можно посмотреть здесь: ilito.paperpaper.ru. Мне понравилось, и я непременно буду использовать Meteor в дальнейшем.
Если у вас есть какие-то вопросы, пишите в комментариях или мне в твиттер @thought_sync.
Автор: Terminal