Оффлайн режим в клиент-серверных приложениях

Игры, да и приложения в целом, часто подразумевают наличие механик, завязанных на сервер. Сейчас не часто встретишь оффлайн игру. Как минимум, игры имеют различные социальные составляющие: кланы, чаты, список друзей, да что угодно. И каждый пук уходит в аналитику.

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

Параллельная разработка по контракту

На этапе планирования задачи, команды договариваются о некоем контракте. Клиентские разработчики говорят что им необходимо. Серверные разработчики прикидывают как это сделать. В конечном счете договариваются о неком API. И каждый уходит пилить свою часть.

Плюсы:

  • Ускорение за счет параллельной разработки
  • Работа ведется относительно независимо, сходясь только в точке контракта

Минусы:

  • Требует опытной комманды
  • Требует налаженной коммуникации между юнитами

Немного подробнее про минусы. Если команда не очень опытная, или коммуникация между юнитами не настроена как следует, то после завершения работы над фичой одной из команд, обычно необходимо еще несколько итераций, так как невозможно в контракте предусмотреть все заранее, если команды не “притёрты”.

Ведущий/ведомый

Один из юнитов в разработки может быть ведущим, и работать на опережение. Этот юнит должен быть на острие бизнес-требований. Обычно это фронтэнд часть, но зависит от продукта.

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

Плюсы:

  • Меньше издержек из-за меняющихся требований
  • Более быстрые итерации, так как все “пилоты” делаются одним юнитом
  • При переходе к следующему этапу разработки, на руках есть почти финальные требования и воркфлоу

Минусы:

  • Разработка ведется последовательно
  • Нужно подготовить разработку таким образом, чтобы разработка могла вестись независимо от другого юнита

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

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

Встает вопрос: как же разрабатывать фичу, которая завязана на сервер, но без сервера?

Ответ прост: сделать оффлайн режим с помощью заглушек.

Оффлайн режим

Что я понимаю под оффлайн режимом? Я не имею ввиду, что игра должна поддерживать оффлайн геймплей. Если это не заложено в дизайн изначально, то переводить все на поддержку оффлайна – безумство.

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

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

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

Если вы уже используете Dependency Injection, или ваши зависимости представлены в виде интерфейсов, то тут все совсем просто.

Все взаимодействие с сервером вы пускаете через Фасад (Facade). При билде делается настройка “isOffline”, и если она включена, подставляются заглушки для всех сетевых сервисов.

Offline mode class diagram

На диаграмме я показал, что API может быть разделен на разные компоненты, при этом фасад их скрывает. Каждый из компонентов имеет оффлайн заглушку.

Для заглушек есть несколько вариантов поведения:

  1. Возвращать один и тот же ответ
  2. Реализовывать простую бизнес логику

Первый вариант совсем бесполезный. Он не поможет погонять какую-либо фичу в оффлайне. Второй же вариант вполне годный. Но хранить стейт, пока не убьешь приложение – тоже не очень полезно. Поэтому отдельно я вынес заглушку для сериализации стейта, которую могут использовать стабы.

Такой подход позволить написать и детально потестить бизнес логику без всякого участия серверных разработчиков и привязки к какой-либо инфраструктуре. Плюс ко всему, вы можете писать код где угодно, даже без доступа к интернету (в самолете?).

Когда код будет завершен с клиентской стороны, серверные разработчики могут смотреть в оффлайн имплементацию и иметь представление о том, что приложение ожидает от серверного API и конкретно как его использует.

Конечно у оффлайн режима есть и свои минусы:

  • Нужно поддерживать две реализации: онлайн и оффлайн
  • Легаси приложениям может быть сложно переехать на оффлайн режим
  • Если бизнес логика сложна, а на клиенте ее присутствие в продакшене не планируется, то может быть очень накладно делать ее на клиенте, тут уж надо искать обходные пути

Гибридный режим

Есть еще один вариант, когда полный оффлайн режим реализовывать очень проблемно. Это гибридный режим. Под таким режимом я понимаю, что все API работает как обычно, а при реализации новой фичи, часть, завязанная на сервер стаббится. Как только с реализацией фичи на клиенте закончено, стаб убирается и тестится с сервером. Наличие юнит и интеграционных тестов позволяет довольно быстро все проверить.

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

Подводим итоги

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

У оффлайн режима есть много всяких побочных плюшек, таких как локальное сохранение прогресса на устройстве. Конечно же у такого подхода есть и недостатки.

Надеюсь статья натолкнула вас на интересные мысли :). Пишите, всегда рад поболтать и услышать про другие решения, которые работают для вас.