Простой бэкенд для игры
Часто при разработке игры требуется сделать серверную часть. Сегодня я покажу как просто и быстро можно это сделать на примере хранения таблицы рекордов.
Использовать буду .net core 2.0 потому, что с одной стороны это позволяет писать на C#, с другой код можно запустить на любой дешевой виртуалке.
В качестве фреймворка я буду использовать NancyFx. Не asp.net core потому, что я считаю его не production-ready.
Начнем с установки необходимых пакетов
dotnet add package Nancy --version 2.0.0-clinteastwood dotnet add package Nancy.Hosting.Self --version 2.0.0-clinteastwood
Первый это собственно NancyFx, второй позволяет хостить веб сервис в самом приложении. Это позволит избежать установки дополнительных веб серверов и их настройки (хотя NancyFx умеет хоститься как в IIS, так и в nginx).
Теперь стартуем серверную
static void Main(string[] args) { using (var host = new NancyHost(new Uri("http://localhost:1236"))) { host.Start(); Console.WriteLine("Running on http://localhost:1236"); Console.ReadLine(); } }
Все, бэкенд уже стартовал. Осталось научить его обрабатывать запросы.
Обработка запросов
Запросов будет 2:
GET запрос на «/» — получение 5 лучших результатов
POST запрос на «/» — сохранение результата игрока
Запросы в NancyFx обрабатывают модули. Для того, чтобы добавить модуль, нужно написать класс, унаследованный от NancyModule
public class ScoreModule : NancyModule { public ScoreModule(IScoreRepository scoreRepository) { Get("/", args => { return scoreRepository.GetTopScores(); }); Post("/", args => { try { var player = (string)Request.Form.player; var score = (int)Request.Form.score; scoreRepository.AddScore(new ScoreModel(player, score)); return 200; } catch { return 500; } }); } }
Регистрировать модуль нигде не нужно, NancyFx сам найдет все модули в сборке.
NancyFx очень гибко относится к возвращаемым значениям. В коде выше return 200 отработает как возврат кода 200, в то время, как при возврате экземпляра класса, он будет сериализован в JSON.
ScoreModel в этом примере выглядит так:
public class ScoreModel { public string Player { get; private set; } public int Score { get; private set; } public Guid Id { get; set; } public ScoreModel(){} public ScoreModel(string player, int score) { this.Player = player; this.Score = score; } }
Свойство Guid Id добавлено для совместимости с LiteDB, в которой будут храниться данные.
Хранение данных
Данные будут храниться в LiteDB потому, что это позволяет быстро разрабатывать и легко деплоить.
Выше упоминался IScoreRepository, выглядит он так:
public interface IScoreRepository { void AddScore(ScoreModel score); IEnumerable<ScoreModel> GetTopScores(); }
И реализация:
public class ScoreRepository : IScoreRepository { private LiteDatabase database; private LiteCollection<ScoreModel> scoreCollection; public ScoreRepository() { database = new LiteDatabase("data.db"); scoreCollection = database.GetCollection<ScoreModel>("data"); } public void AddScore(ScoreModel score) { if (score != null) scoreCollection.Insert(score); } public IEnumerable<ScoreModel> GetTopScores() { return scoreCollection.FindAll() .OrderByDescending(x => x.Score).Take(5).ToList(); } }
Как видно в коде модуля, он принимает в конструкторе экземпляр IScoreRepository. Это значит, что нужно где-то зарегистрировать нашу реализацию этого интерфейса. NancyFx содержит свой DI контейнер, но при этом позволяет использовать любой другой сторонний.
Для регистрации зависимости нужно переопределить бутстрапер. Bootstrapper — это класс, который содержит всю логику настройки приложения в NancyFx. Как и с модулем, достаточно создать класс, переопределяющий базовый DefaultNancyBootstrapper. NancyFx сам найдет этот класс.
public class Bootstrapper : DefaultNancyBootstrapper { protected override void ConfigureRequestContainer( TinyIoCContainer container, NancyContext context) { base.ConfigureRequestContainer(container, context); container.Register<IScoreRepository, ScoreRepository>() .AsSingleton(); } }
Мы переопределяем только один метод, в котором и регистрируем зависимость.