Мотиватция
Однажды в студеную зимнюю пору течении очередного планнинг-митинга на работе, где каждый пользовался своим инструментом для голосования — бумажные карты, всевозможные приложения для телефонов, etc — меня посетила мысль — а зачем сидеть всем в одном помещении, когда планирование можно проводить со своих рабочих мест, или даже из дома.
Итак идея — сделать сервис удаленного планирования, посредством техники Planning Poker, так популярной в agile мире. А также чуть лучше разобраться с тем как работает socket.io и сопутствующие технологии.
Постановка задачи
Разработать страницу заходя на которую пользователь вводит свое имя и «комнату», далее один из пользователей устанавливает обсуждаемую задачу, все голосуют — когда проголосовал последний из группы — карты открываются.
Первая попытка реализации протокола
Итак начитавшись этой статьи — первая мысль: браузер — тонкий клиент, вся логика и сосотояние хранятся на сервере, как только подклучается новый клиент, в определенную комнату — сервер рассылает всем новый снапшот состояния комнаты. Тоже происходит когда клиенты голосуют, меняют тему обсуждения и т.д.
Однако такой подход требует какого-то хранилища на сервере, (мне хотелось избежать использования базы данных, так как хостить прект хотелось по возможности бесплатно). Как вариант хранить состояние в памяти — массиве или некоем сложном объекте, но это чревато утечками памяти.
Вторая и финальная реализация
Вторым подходом к решению задачи было сделать каждый отдельный клиент ответсвенным за хранение своего собственного состояния и обмен состояниями со всеми участниками. Если абстрагориваться от socket.io то протокол можно обяснить следующим образом — вежливые люди в темной комнате:
- когда кто-то входит
join
— он объявляет об этом и называет свое имя — в ответ все остальные люди в комнате называют свои имена и состояния. - когда кто-то называет свое (обновившееся) состояние
update
— все в комнате запомианют его. - когда кто-то уходит из комнаты
leave
— сервер сообщает об этом всем остальным учасникам и они благополучно о нем забывают.
последний пункт не вписывается в правила вежливости — так как клиент не может оповестить всех о своем отключении, эту фунцию пришлось возложить на сервер — именно сервер отвественнен за то чтоб сообщить другим об уходе одного из участников:
socket.on('disconnect', function () {
var rooms = that.__io.sockets.manager.roomClients[socket.id];
for(var room in rooms) {
if ((room != '') && (rooms[room])) {
/*removing a slash*/
room = room.substring(1);
that.__io.sockets.in(room).emit('leave', { room : room, id : socket.id });
}
}
});
Отдельно надо упомянуть поддержку комнат в socket.io, о которой нислова не упоминается на на офиц. сайте. Я много информации почерпнул отсюда. По большому счету комнаты есть некий логический способ разделить всех клиентов подклученных к серверу.
Вступить в комнату или покинуть ее можо только с серверной части, одновременно можно находится в нескольких комнатах:
socket.join('room');
socket.leave('room');
В остальном север — всего лишь проксирует сообщения между клиентами в одной комнате. Единственная «бизнес логика» на сервере — это ограничение на вхождение в комнаты до 12 человек. Например пересылка сообщения update на севере выглядит так:
/*sending update*/
socket.on('update', function (data) {
/*notify everyone*/
data = that.identify(data, socket);
that.__io.sockets.in(data.room).emit('update', data);
});
Клиент
Вся логика у нас происходит на клиенте. Он ответвственнен за голосование клиента (выбор карты), принимает решение когда показывать карты остальных участников, сбрасывает голоса когда меняется топик и/или приходят новые люди. Клиент также публикует события — это сделано для того чтобы отделить бизнес логику клиента от собственно манипуляций с DOMом и т.п. Выглядит это так:
client.on('reveal', function(){
$('.flippable').each(function(i, val) {
setTimeout(function(){
$(val).addClass('flipped');
}, i*50);
});
});
Заключение и демонстрация
В заклюцении хочу сказать что подобный подход хоть и проявил себя не плохо, не лишен недостатков — т.е. есть возможность влезть в логику клиента и таким образом нарушить синхронизацию бежду учасниками. Так же голоса прибывают сразу как только ктото проголосовал, они только не видны пока все не проголосовали, однако не хитрыми манипуляциями в Firebug можно подглядывать голоса других.
В качестве развития можно реализовать, вычисление максимального, минимального и среднего значения карт, а также реализовать простенький чат между людьми в комнате — для обсуждения топиков.
как оказалось в самый последний момент Heroku не поддерживает websockets, и пришлось скатиться до long polling — посему предлагаемое вам демо — лишено одной важной возможности — клиент вовремя не узнает что один из участников отключился, это событие не происходит. Для полноценного ознакомления — я предлагаю вам склонировать этот репозиторий на Github'e и запустить на локальной машине, серверный скрипт делает проверку на Heroku и не находя её, работает в штатном режиме с websocket'ами. Так же для полноценного тестирования понадобится 2 или более участников.
Удачи!
Автор: artem82