Простой бэкенд для игры

20.11.2017 at 08:43

Часто при разработке игры требуется сделать серверную часть. Сегодня я покажу как просто и быстро можно это сделать на примере хранения таблицы рекордов.
Использовать буду .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();
    }
}

Мы переопределяем только один метод, в котором и регистрируем зависимость.