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

в 13:34, , рубрики: Без рубрики

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


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

npm install connect
npm install node-uuid

Также, вместо использования встроенных сессий тут самописный велосипед. Нет работы с базой данных, но про это есть замечательная статья от mail.ru Базы данных в онлайн-играх. От Аллодов Онлайн до Skyforge, а вписать 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.

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

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

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

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

player.money += projectCost;

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

Вывод

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

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

Автор: dbalakov

Источник

Поделиться

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