- PVSM.RU - https://www.pvsm.ru -

Социальные игры: сервер, клиент и общая шина событий

Добрый день.
У меня выдалось свободное время, и я решил написать статью про несправедливо обойденный вниманием паттерн написания api. Он подходит в случае, если у Вас и клиент и сервер содержат сложное состояние, и есть необходимость его синхронизировать. На мой взгляд, социальные игры идеально подходят под это описание. В силу того, что я ещё не достиг того уровня мастерства, когда могу писать статьи про программирование без кода, спешу предоставить пример, демонстрирующий данный подход. Посмотреть код можно на github: https://github.com/dbalakov/studio_game [1].


Немного реверансов про проект. Стиль написания отличается production кода, в силу того, что цель — максимально быстро и наглядно продемонстрировать идею. Например, вместо использования jake [2] Вам придется вручную установить connect [3] и node-uuid [4].

npm install connect
npm install node-uuid

Также, вместо использования встроенных сессий тут самописный велосипед. Нет работы с базой данных, но про это есть замечательная статья от mail.ru Базы данных в онлайн-играх. От Аллодов Онлайн до Skyforge [5], а вписать DAO слой в данный проект можно просто и безболезненно.
Стоит отметить и полное отсутствие тестов, но про тестирование в nodejs будет отдельная статья.

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

Постановка задачи

Мы пишем игру про разработку игр. У нас есть проект, над которым можно работать, а потом когда мы готовы показать проект миру, мы можем продать его и начать новый. У проекта есть 3 основные характеристики: graphics, features, bugs и улучшая каждую из них, мы можем менять выходную стоимость проекта.

Первая медитация

Архитектуру клиент-серверных приложений лучше всего начинать строить с api. Это позволяет увидеть и клиент, и сервер, и, исходя из постановки, у нас получается следующий api:

  • init метод, инициализирующий сессию, и возвращающий клиенту игровые настройки и другую техническую информацию
  • login метод, возвращающий клиенту данные игрока
  • newProject метод, создающий новый проект
  • sellProject метод, продающий текущий проект
  • startWork метод, начинающий работу над проектом
  • completeWork метод, который применяет работу над проектом и меняющий его характеристики

После кружки кофе и внимательного взгляда на этот api, можно понять, что он избыточен: init, login и sellProject, newProject — связанные методы: после init тут же вызывается login, а после продажи проекта мы тут же начинаем новый — поэтому мы можем объединить эти методы. После этого наш api выглядит примерно так:

  • init метод, инициализирующий сессию, и возвращающий клиенту игровые настройки, данные игрока и другую техническую информацию
  • newProject метод, продающий существующий проект и создающий новый
  • startWork метод, начинающий работу над проектом
  • completeWork метод, который применяет работу над проектом и меняющий его характеристики

Реализацию мы начнем с сервера и, конечно, со слоя модели. То, что у меня получилось, можно посмотреть в папке models [6].

Вторая медитация

Если Вы внимательно посмотрите на своего руководителя проекта, то заметите искорки в его глазах: это новые, пока еще нерожденные идеи и фичи, которые потом придется включать в код, поэтому для упрощения добавления новых методов в api мы вспомним заповедь DRY [7] и поймем, что дублировать описание методов и на клиенте и на сервере — это избыточно. Пусть метод init возвращает список и описание методов, по которому мы и построим объект api. То, что у меня получилось, можно посмотреть тут: реализация метода на стороне сервера [8]. Но это грозит дополнительной нагрузкой на сервер, и, чтобы уменьшить ее, мы кэшируем ответ. Реализация на клиенте тут [9]

Третья медитация

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

player.money += projectCost;

Но во время предыдущей медитации мы уже вспоминали принцип DRY [7], а писать на клиенте и на сервере одно и то же является явным его нарушением. Для того, чтобы упростить код обработчиков ответов сервера и избежать идентичных строк в коде клиента и сервера, мы введем шину событий, общую для клиента и сервера, то есть модель на сервере сама будет следить за своими изменениями и уведомлять о них клиент. На сервере за это отвечает model [10], и если в коде Вы используете сеттеры, а не обращаетесь напрямую к свойствам объекта, Вы автоматически получите актуальную модель и на клиенте.
На клиенте же обработчик событий можно написать один раз, причем в данной реализации он даже не зависит от структуры модели.

Вывод

После трех несложных медитаций мы получили проект с «пластилиновым» кодом, который легко читать и расширять. А если у нас не 4 серверных метода, а хотя бы штук 30, то мы получим выигрыш не только в читаемости, но и в размере кода. Поэтому я призываю всех коллег потратить хотя бы час-два на обдумывание задачи за листочком, прежде чем бежать создавать новый проект и быстро-быстро писать код.

P.S. Моей кошке не понравилась первая версия статьи и она хамски ее удалила, сославшись на полную нечитабельность. Надеюсь, я учел ошибки, и статья стала понятной.

Автор: dbalakov

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/news/40180

Ссылки в тексте:

[1] https://github.com/dbalakov/studio_game: https://github.com/dbalakov/studio_game

[2] jake: https://github.com/mde/jake

[3] connect: http://www.senchalabs.org/connect/

[4] node-uuid: https://github.com/broofa/node-uuid

[5] Базы данных в онлайн-играх. От Аллодов Онлайн до Skyforge: http://habrahabr.ru/company/mailru/blog/182088/

[6] models: https://github.com/dbalakov/studio_game/tree/master/models

[7] DRY: http://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself

[8] реализация метода на стороне сервера: https://github.com/dbalakov/studio_game/blob/master/api/commands/init.js

[9] тут: https://github.com/dbalakov/studio_game/blob/master/public/js/api.js

[10] model: https://github.com/dbalakov/studio_game/blob/master/models/model.js

[11] Источник: http://habrahabr.ru/post/188928/