Фреймворк NancyFX и сервисы в стиле REST

в 14:55, , рубрики: .net, C#, архитектура, Блог компании Издательский дом «Питер», микросервисы, рефакторинг

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

Если вдруг вас совсем не интересует фреймворк NancyFX и микросервисы на платформе .NET, создаваемые с его помощью — почитайте про динозавров!

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

Чтобы это получалось еще лучше, мы постепенно переделали наш сегмент базы кода под микросервисную архитектуру. Первым делом мы написали API Reservations, выделив весь функционал, связанный с созданием и извлечением записей о бронировании в отдельный домен с независимыми системами версионирования, релизов и обслуживания. Так нам удалось без труда поддерживать новые кроссплатформенные фронтенды – например, мобильную версию и форму бронирования на основе Node.js

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

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

NancyFx – потрясающий легкий веб-фреймворк для .NET. Если вы ориентируетесь во фреймворках для других языков, например, Sinatra и Express, то уже вполне представляете, что от него ожидать. С другой стороны, если знакомы лишь с такими .NET-глыбами как Microsoft MVC и WCF, то, вполне возможно, вас ждет приятный сюрприз.

Вместо того, чтобы распевать дифирамбы Nancy (я это могу делать часами), лучше продемонстрирую вам, насколько проще пареной репы написать на Nancy простейшую оконечную точку в стиле REST менее чем за 15 минут.

Шаг 1: Создаем консольное приложение на C#

Разумеется, это можно сделать и на другом языке для платформы .NET. Даже на VB.

Шаг 2: Импортируем Nancy

Nancy предоставляется в виде пакетов Nuget, так что рекомендую воспользоваться менеджером пакетов Visual Studio – самым удобным инструментом для импорта двоичных файлов в проект и ссылки на них. В данном случае нам понадобится пакет Nancy.Hosting.Self, зависящий от основного пакета Nancy.

Шаг 3: Создаем хост Nancy

В методе Main (или эквивалентной входной точке программы) напишите:

using (var host = new NancyHost(new Uri("http://localhost:1234"))
{
    Console.ReadKey();
}

Вы уже создали консольное приложение, которое слушает HTTP на порте 1234. На самом деле, нам нравится так делать, это простая реализация обратных прокси, передающих внешние HTTP-запросы управляемому процессу. Однако, Nancy поддерживает и OWIN, традиционный IIS-хостинг.

Шаг 4: Создаем маршрут к ресурсу

Создаем наш первый маршрут, наследуя класс Module из Nancy. Напишите вот это в файле с новым проектом:

class Dinosaur
{
    public string Name { get; set; }
    public int HeightInFeet { get; set; }
    public string Status { get; set; }
}

class DinosaurModule : NancyModule
{
    private static Dinosaur dinosaur = new Dinosaur()
    {
        Name = "Kierkegaard",
        HeightInFeet = 0,
        Status = "Deflated"
    };

    public DinosaurModule()
    {
        Get["/dinosaur"] = parameters => dinosaur;
    }
}

При запуске хоста Nancy просматривает вашу сборку и ищет в ней классы, наследующие NancyModule. Они будут инстанцироваться всякий раз при поступлении запроса, обеспечивать маршрутизацию и действия. В данном случае мы создаем простой маршрут GET в конструкторе модуля и пользуемся лямбда-выражением, при помощи которого возвращаем определенный нами объект модели. Если вызвать localhost:1234/dinosaur без заголовка с типом содержимого, то модель динозавра придет нам в формате JSON. Запустите приложение, попробуйте.

Шаг 5: Добавляем операцию записи

До сих пор наш ресурс был только для чтения. Добавьте в конструкторе DinosaurModule такой код:

Post["/dinosaur"] = parameters =>
{
    var model = this.Bind<Dinosaur>();
    dinosaur = model;
    return model;
};

Привязка принимает тело HTTP-запроса и пробует ассоциировать его с заданным типом модели. Попытайтесь послать следующий код на localhost:1234/dinosaur, тип содержимого — application/json:

{
    "name": "Kierkegaard",
    "heightInFeet": 6,
    "status": "Inflated"
}

Тело запроса следует привязать к классу Dinosaur и присвоить нашему статическому члену Dinosaur. Как и в случае с конечной точкой Get, мы возвращаем модель, после чего Nancy сериализует модель JSON. В Nancy есть такие возможности как обсуждение содержимого и рендеринг представления, но в данном демонстрационном примере нам вполне подойдет поведение, задаваемое по умолчанию.

Шаг 6: Создаем и возвращаем индексированные ресурсы

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

public class DinosaurModule : NancyModule
{
    private static List<Dinosaur> dinosaurs = new List<Dinosaur>()
    {
        new Dinosaur() {
           Name = "Kierkegaard",
           HeightInFeet = 6,
           Status = "Inflated"
        }
    };

    public DinosaurModule()
    {
        Get["/dinosaurs/{id}"] = parameters => dinosaurs[parameters.id - 1];
        Post["/dinosaurs"] = parameters =>
        {
            var model = this.Bind<Dinosaur>();
            dinosaurs.Add(model);
            return dinosaurs.Count.ToString();
        };
    }
}

Теперь маршрут - /dinosaurs. Объект parameters в лямбда-выражении – это, в сущности, динамический тип, комбинирующий значения из маршрута, строки запроса и тела запроса. Определяя параметр {id} в рамках маршрута, можно захватывать его, а потом с его помощью извлекать нужный нам ресурс.

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

Шаг 7: Ваш ход

В финале нашей весьма обзорной экскурсии у нас получилась такая спартанская оконечная REST-точка. А что дальше? Естественно, мы хотим держать наши ресурсы в каком-то долговременном хранилище данных, а не в памяти, но что насчет валидации, обработки ошибок, безопасности?

При всей легкости и простоте Nancy прямо «из коробки» решает и многие подобные вопросы, а также предоставляет другие возможности, которые вы сможете с легкостью внедрить в проект.
Итак, общее представление о Nancy вы составили – а теперь почитайте документацию.

Автор: Издательский дом «Питер»

Источник

Поделиться

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