Статьи

Адаптация RethinkDB для вечерней паутины с толкателем

Pusher_rethinkDB_demo

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

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

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

  • Каналы идентифицируют данные, будь то таблица в базе данных или, в случае RethinkDB, подача изменений.
  • События представляют, что происходит с данными: новые данные доступны, существующие данные обновляются или данные удаляются.

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

Чтобы показать, как это можно сделать, в этом посте вы узнаете, как создать тип потоков активности, найденных на веб-сайте RethinkDB . Создав небольшое приложение Sinatra, мы быстро создадим JSON-фид и список рекордов, которые вы можете увидеть в нашей демонстрации . Обратите внимание, что хотя мы используем Ruby и Sinatra, одна из замечательных особенностей адаптеров RethinkDB заключается в том, насколько они похожи во всех языках. Другими словами, то, что мы здесь делаем, может быть легко применено к выбранному вами стеку.

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

Шаг 1: Настройка

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

Если у вас не установлен RethinkDB, вы можете установить его на Mac с помощью Homebrew :

$ brew install rethinkdb 

Установка для других операционных систем может быть найдена в документации .

Чтобы запустить сервер RethinkDB, введите в терминал:

 $ rethinkdb 

Теперь перейдите к http: // localhost: 8080 / # tables , что ведет к веб-интерфейсу, где вы можете создать свою базу данных. Создайте базу данных под названием game со столом players .

В каталоге вашего проекта добавьте гемы Pusher и RethinkDB в ваш Gemfile вместе с Sinatra для нашего веб-приложения:

 gem 'pusher' gem 'rethinkdb' gem 'sinatra' 

В комплекте установите ваши гемы и создайте app.rb для обработчиков маршрутов и rethinkdb.rb для конфигурации базы данных.

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

 require 'rethinkdb' include RethinkDB::Shortcuts require 'pusher' pusher = Pusher::Client.new({ app_id: , key: secret: }) $conn = r.connect( host: "localhost", port: 28015, # the default RethinkDB port db: 'game', )  require 'rethinkdb' include RethinkDB::Shortcuts require 'pusher' pusher = Pusher::Client.new({ app_id: , key: secret: }) $conn = r.connect( host: "localhost", port: 28015, # the default RethinkDB port db: 'game', )  require 'rethinkdb' include RethinkDB::Shortcuts require 'pusher' pusher = Pusher::Client.new({ app_id: , key: secret: }) $conn = r.connect( host: "localhost", port: 28015, # the default RethinkDB port db: 'game', )  require 'rethinkdb' include RethinkDB::Shortcuts require 'pusher' pusher = Pusher::Client.new({ app_id: , key: secret: }) $conn = r.connect( host: "localhost", port: 28015, # the default RethinkDB port db: 'game', ) 

В app.rb настройте основные приложения Sinatra:

 require 'sinatra' require './rethinkdb' get '/' do erb :index end 

Шаг 2: Создание игроков

Как вы можете видеть в демоверсии , всякий раз, когда игрок вводит свое имя, начинается игра Snake. Тем временем мы хотим создать экземпляр проигрывателя из имени, предоставленного пользователем.

Эта демонстрация не будет углубляться в HTML и jQuery за приложением. Игра Snake основана на заимствованном коде (который вы можете прочитать здесь ) и отвлечет от цели урока: доставка последней мили в несколько строк кода. Но если вы хотите углубиться в это, не стесняйтесь проверить исходный код .

В случае создания пользователей мы просто хотим отправить AJAX POST для /players с {name: "the user's name"} на сервер Sinatra. В этой конечной точке приложение выполняет простой запрос insert в таблицу players :

 post '/players' do name = params[:name] r.table("players").insert(name: name).run($conn) # pass in the connection to `run` "Player created!" end 

Это так просто! Если вы запустите приложение и перейдете по адресу http: // localhost: 8080 / # dataexplorer , запустив r.table("game").table("players") , вы должны увидеть свой новый документ игрока.

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

 post '/players' do name = params[:name] response = r.table("players").insert(name: name).run($conn) session[:id] = response["generated_keys"][0] "Player created!" end 

Шаг 3: Отправка очков игроков

Чтобы сделать демонстрацию более увлекательной и интерактивной, я добавил два события jQuery в код змеи. Один для запуска начала игры после того, как игрок был создан, а второй — для прослушивания конца игры и получения счета пользователя .

Когда игра заканчивается, счет передается слушателю события jQuery. Используя этот счет, создайте простой POST для /players/score с помощью параметров {score: score} Score {score: score} . Обработчик маршрута получает игрока по его идентификатору сеанса и обновляет его счет и рекорд, соответственно:

 post '/players/score' do id = session[:id] score = params[:score] player = r.table("players").get(id).run($conn) # get the player score_update = {score: score} # our update parameters if !player["score"] || score > player["high_score"] # if the player doesn't have a score yet # or if the score is higher than their highest score score_update[:high_score] = score # add the high-score to the query end r.table("player").get(id).update(score_update).run($conn) # eg .update(score: 94, high_score: 94) {success:200}.to_json end 

Теперь, когда в нашей базе данных есть ключ high_score для players , мы можем начать рендеринг статического представления таблицы лидеров, прежде чем мы сделаем его в реальном времени. В rethinkdb.rb давайте создадим наш запрос списка лидеров:

 LEADERBOARD = r.table("players").order_by({index: r.desc("high_score")}).limit(5) 

Чтобы это работало, убедитесь, что вы создали индекс с именем high_score через который можно заказать players . Это можно сделать в обозревателе данных RethinkDB , запустив r.db("game").table("players").indexCreate("high_score") .

Приложению требуется конечная точка /leaderboard чтобы выводить лидеров в DOM:

 get '/leaderboard' do leaders = LEADERBOARD.run($conn) leaders.to_a.to_json end 

Используя jQuery или предпочитаемую платформу Javascript, покажите статический список игроков с наибольшим количеством очков:

 $.get('/leaderboard', function(leaders){ showLeaderboard(leaders); // showLeaderboard can render leaders in the DOM. }) 

Шаг 4: Сделайте вашу базу данных в реальном времени

Как вы можете видеть из демонстрации , у нас есть два потока в реальном времени: необработанный поток JSON текущих результатов и таблица лидеров. Чтобы начать слушать эти изменения, мы будем использовать адаптер гема RethinkDB для EventMachine. В начало rethinkdb.rb добавьте require 'eventmachine' . Sinatra перечисляет EventMachine как зависимость, поэтому он уже должен быть доступен в контексте вашего пакета.

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

 EventMachine.next_tick do # usually `run` would be sufficient - `next_tick` is to avoid clashes with Sinatra's EM loop LEADERBOARD.changes.em_run($conn) do |err, change| updated_player = change["new_val"] pusher.trigger(“scores†, "new_high_score", update_player) end end 

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

 { "new_val": { "high_score": 6 , "id": "476e4332-68f1-4ae9-b71f-05071b9560a3" , "name": "thibaut courtois" , "score": 6 }, "old_val": { "high_score": 2 , "id": "476e4332-68f1-4ae9-b71f-05071b9560a3" , "name": "thibaut courtois" , "score": 1 } } 

В этом случае мы просто берем new_val изменения — то есть самый последний достигнутый рекорд — и запускаем событие Pusher с именем new_high_score на канале, называемом scores с этим обновлением. Вы можете проверить, работает ли он, изменив высокий балл игрока, либо в своем приложении, либо в проводнике данных RethinkDB, а затем перейдите на консоль отладки по адресу http://app.pusher.com, чтобы просмотреть только что созданное событие.

Необработанный поток результатов JSON, показанный в нашем демо, также прост в реализации. Давайте просто построим запрос и поместим его в тот же блок EventMachine:

 LIVE_SCORES = r.table("players").has_fields("score") # all the players for whom `score` isn't `nil` EventMachine.next_tick do ... LIVE_SCORES.changes.em_run($conn) do |err, change| updated_player = change["new_val"] pusher.trigger(“scores†, "new_score", updated_player) end end 

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

 { "high_score": 1, "id": "97925e44-3e8f-49cd-a34c-90f023a3a8f7", "name": "nacer chadli", "score": 1 } 

Шаг 5: Из БД в ДОМ

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

 var pusher = new Pusher("YOUR_APP_KEY"); var channel = pusher.subscribe("scores"); channel.bind("new_score", function(player){ // append `player` to the live JSON feed of scores }); channel.bind("new_high_score", function(player){ // append `player` to the leaderboard }); 

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

Идти вперед

Надеемся, что мы дали вам представление о хорошем языке запросов RethinkDB и потрясающих возможностях реального времени. Мы также показали, как Pusher может быть легко интегрирован для работы в режиме «последней мили» от изменений в вашей базе данных до вашего клиента. Помимо настройки самого приложения, для запуска и запуска изменений не требуется много кода.

Демонстрация, которую я вам показал, довольно маленькая и простая. Большие производственные приложения вполне возможны там, где преимущества интеграции Pusher с RethinkDB ощущаются сильнее. RethinkDB позволяет легко масштабировать базу данных, а Pusher обеспечивает масштабируемость обмена сообщениями в реальном времени. Объединение этих двух компонентов позволяет разработчикам создавать мощные и надежные масштабируемые приложения