Веб-сервисы в .NET

Создание кросс-платформенных веб-сервисов с помощью ServiceStack

Нян Ле

Исходный код можно скачать по ссылке.

Продукты и технологии:

Windows Communication Foundation, ServiceStack

В статье рассматриваются:

  • типичная структура веб-сервисов;
  • применение RPC (Remote Procedure Call) или DTO (Data Transfer Objects);
  • обзор ServiceStack;
  • создание эквивалентных веб-сервисов на основе Windows Communication Foundation (WCF) и ServiceStack;
  • использование встроенных клиентов ServiceStack.

Мне нравится работать с Windows Communication Foundation (WCF), поскольку в Visual Studio обеспечивается великолепная поддержка этой инфраструктуры. Я нахожу довольно простым весь процесс создание веб-сервиса на основе WCF — от начала и до запуска в среде разработки без установки дополнительного инструментария и редистрибутивов. В этой статье я расскажу о своем опыте кросс-платформенной разработки, поэтому она может заинтересовать вас в том случае, если вы уже знакомы с WCF и C# и вам нужно создать кросс-платформенный веб-сервис.

Думаю, мы все сойдемся в том, что написание кросс-платформенных приложений, по крайней мере, создает неудобства, но иногда от этого никуда не деться. Если вы, как и я, привязаны к Windows и вложили много усилий в доскональное изучение C#, то сборка кросс-платформенного веб-сервиса может повлечь за собой значительные издержки. Это включает переконфигурирование вашей любимой Windows-среды для адаптации к совершенно другому набору средств разработки и, возможно, изучение еще одного языка программирования. В этой статье я покажу, как можно использовать WCF-аналог ServiceStack (инфраструктуру веб-сервисов с поддержкой .NET и Mono REST, имеющую открытый исходный код) для достижения той же цели без отказа от Visual Studio или Microsoft .NET Framework.

О веб-сервисах

Структура типичного веб-сервиса показана на рис. 1.

Структура типичного веб-сервиса
Рис. 1. Структура типичного веб-сервиса

 

Web Service Веб-сервис
Service Layer Уровень сервисов
Business Layer Уровень бизнес-логики
Data Layer Уровень данных

Уровень сервисов — то место, где вы определяете интерфейс своего веб-сервиса. Это единственный уровень, с которым взаимодействует клиент, использующий ваш веб-сервис.

Уровень бизнес-логики, очевидно, содержит всю прикладную логику. Здесь находится основная часть реализации веб-сервиса, которая поддерживает клиент-серверные контракты и коммуникации, а также позволяет сохранять уровень сервисов предельно компактным.

Уровень данных инкапсулирует все, что связано с доступом к данным, и предоставляет абстрагированные модели данных для манипуляций с ними на уровне бизнес-логики.

В этой статье я сосредоточусь на уровне сервисов.

Сравнение RPC с DTO

В некоторых веб-сервисах применяется подход RPC (Remote Procedure Call), где каждый запрос напоминает вызов функции:

public interface IService {
  string DoSomething(int input);
}

Подход с RPC обычно делает веб-сервис более чувствительным к разрушающим изменениям интерфейса. Так, в предыдущем фрагменте кода, если более поздней версии веб-сервиса требуются два входных параметра от клиента для выполнения метода DoSomething или нужно возвращать еще одно поле в дополнение к строковому значению, разрушающее изменение в интерфейсе для старых клиентов неизбежно. Конечно, всегда можно создать параллельный метод DoSomething_v2, принимающий два входных аргумента, но со временем это приведет к загромождению интерфейса веб-сервиса и риску запутать ваших потребителей — как старых, так и новых.

Здравый смысл заставляет отдавать предпочтение определению интерфейсов веб-сервиса на основе модели DTO (Data Transfer Object).

Здравый смысл заставляет отдавать предпочтение определению интерфейсов веб-сервиса на основе модели DTO (Data Transfer Object). В следующем коде показано, как можно трансформировать веб-метод DoSomething под модель DTO:

public class DoSomethingRequest {
  public int Input { get; set; }
}
public class DoSomethingResponse {
  public string Result { get; set; }
}
public interface IService {
  DoSomethingResponse DoSomething(DoSomethingRequest request);
}

Следуя в этом направлении, каждый веб-метод принимает один DTO запроса и возвращает один DTO ответа. Идея в том, что добавление новых полей в DTO запроса и ответа не нарушает работу более старых клиентов.

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

Что такое ServiceStack?

Как упоминалось, ServiceStack — кросс-платформенная инфраструктура веб-сервисов Mono с открытым исходным кодом, и она набирает популярность. Веб-сервисы, созданные с помощью ServiceStack, могут выполняться в среде Windows с .NET-кодом или в среде Linux с поддержкой Mono. Mono поддерживает следующие операционные системы:

  • Linux;
  • Mac OS X, iOS;
  • Sun Solaris;
  • BSD;
  • Microsoft Windows;
  • Nintendo Wii;
  • Sony PlayStation 3.

Подробнее о платформах, поддерживаемых Mono, см. по ссылке mono-project.com/Supported_Platforms.

Если вам нравится работать с .NET Framework и WCF и нужно развертывать свои веб-сервисы .NET в среде, отличной от Windows, то ServiceStack — идеальный выбор. Благодаря сходству ServiceStack и WCF переход с одной инфраструктуры на другую потребует лишь небольших изменений в среде разработки и инструментарии. Вы сможете по-прежнему писать код на C# в Visual Studio.

ServiceStack вводит для интерфейса веб-сервисов основанные на соглашениях стандарты DTO, тогда как WCF дает вам полную свободу в определении API веб-сервиса. ServiceStack предоставляет готовый объект статуса ответа (response status object), который можно использовать при композиции DTO ответа, что способствует созданию более прямой и простой схемы обработки ошибок. Хотя это легко реализуется и в WCF, такой путь в ней далеко не очевиден. Последние версии ServiceStack также предоставляют механизм обработки ошибок на основе исключений, похожий на обработку ошибок в WCF на основе контрактов сбоев (fault contracts), но не столь изощренный.

Стандарты, вводимые ServiceStack, легко реализуются в WCF, и для этого достаточно написать немного дополнительного кода. Однако, помимо портируемости, ServiceStack важен для создания веб-сервиса RESTful, поскольку устанавливает соглашения, которые упрощают маршрутизацию HTTP URI. В то же время ServiceStack заставляет реализовать каждый запрос веб-сервиса в отдельном классе, естественным образом способствуя разделению обязанностей в модели сервисов RESTful.

ServiceStack предлагает и другие возможности, например готовую утилиту для протоколирования и базовой проверки данных. Подробнее о ServiceStack см. по ссылке servicestack.net.

В этой статье предполагается, что вы в какой-то мере знакомы с WCF и .NET Framework. Чтобы лучше продемонстрировать, как концепции WCF можно привести к концепциям ServiceStack, я сначала реализую уровень сервисов в WCF. Затем я покажу, как преобразовать веб-сервис на основе WCF в кросс-платформенный веб-сервис, перенеся его в эквивалентный веб-сервис, который использует ServiceStack.

Создание простого веб-сервиса на основе WCF

Чтобы продемонстрировать, как ServiceStack может послужить заменой WCF в среде с несколькими платформами, я начну с простого веб-сервиса на основе WCF.

Данный веб-сервис является тривиальной системой резервирования столика в ресторане и называется TicketService, который реализует следующий контракт сервиса:

[ServiceContract]
public interface ITicketService {
  [OperationContract]
  List<Ticket> GetAllTicketsInQueue();
  [OperationContract]
  void QueueTicket(Ticket ticket);
  [OperationContract]
  Ticket PullTicket();
}

TicketService позволяет своим клиентам ставить в очередь новый талон (ticket), извлекать его из очереди и получать полный список всех талонов, находящихся в очереди на данный момент.

Этот веб-сервис состоит из трех проектов Visual Studio (рис. 2) (для запуска моего решения-примера, которое можно скачать по ссылке archive.msdn.microsoft.com/mag201308Services, требуется Visual Studio 2012 с Web Developer Tools и .NET Framework 4.5).

Проекты Visual Studio для WCF-сервиса резервирования столиков в ресторане
Рис. 2. Проекты Visual Studio для WCF-сервиса резервирования столиков в ресторане

Ниже перечислены эти три проекта:

  • TicketSystem.ServiceContract — определяет интерфейс сервиса;
  • TicketSystem.TicketProcessor — содержит детали реализации прикладной логики веб-сервиса; в нем нет ссылок на WCF;
  • WcfServer — реализует TicketSystem.ServiceContracts и размещает веб-сервис в IIS.

Я также создал WCF-клиент для работы с TicketService. WCF-клиент является консольным приложением, которое использует код, сгенерированный в результате добавления ссылки WCF-сервиса в TicketService, для взаимодействия с веб-сервисом с помощью SOAP поверх IIS. Вот консольная реализация клиента:

static void Main(string[] args) {
  Console.Title = "WCF Console Client";
  using (TicketServiceClient client = new TicketServiceClient())  {
    Ticket[] queuedTickets = client.GetAllTicketsInQueue();
    foreach (Ticket ticket in queuedTickets) {
      PrintTicket(ticket);
    }
  }
}

Реализация клиента обслуживает информацию, показанную на рис. 3.

Консольный WCF-клиент
Рис. 3. Консольный WCF-клиент

Создание эквивалентного веб-сервиса на основе ServiceStack

Требования для работы с ServiceStack Чтобы работать с ServiceStack, нужно сначала скачать необходимые редистрибутивы. Самый простой способ сделать это — воспользоваться расширением NuGet для Visual Studio, которое позволяет легко устанавливать и обновлять сторонние библиотеки и инструменты. Вы можете скачать и установить клиент NuGet с nuget.codeplex.com. После установки NuGet вы должны увидеть в Visual Studio элементы меню, показанные на рис. 4.

NuGet Package Manager Console в Visual Studio
Рис. 4. NuGet Package Manager Console в Visual Studio

Теперь мы пошагово пройдем всю процедуру создания веб-сервиса на основе ServiceStack, эквивалентного ранее созданному веб-сервису на основе WCF.

В качестве отправной точки возьмем WCF-версию примера веб-сервиса. Затем удалим из решения проекты WcfClient и WcfServer. Эти проекты специфичны для WCF, и они будут потом заменены проектами, совместимыми с ServiceStack.

В окне Package Manager Console выберите проект TicketSystem.ServiceContract. В командной строке введите install-package ServiceStack, как показано на рис. 5.

Установка редистрибутивов ServiceStack с помощью NuGet Package Manager
Рис. 5. Установка редистрибутивов ServiceStack с помощью NuGet Package Manager

Это приводит к скачиванию последних версий редистрибутивов, необходимых для создания веб-сервиса .NET, использующего ServiceStack. Редистрибутивы помещаются в папку Packages в корневой папке вашего решения Visual Studio. Кроме того, в проект TicketSystem.ServiceContract добавляются ссылки на DLL. Также создается файл packages.config в корневой папке вашего проекта, который предоставляет информацию о версии и исполняющей среде для каждой ServiceStack DLL:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="ServiceStack" version="3.9.46"
    targetFramework="net45" />
  <package id="ServiceStack.Common" version="3.9.46"
    targetFramework="net45" />
  <package id="ServiceStack.OrmLite.SqlServer" version="3.9.45"
    targetFramework="net45" />
  <package id="ServiceStack.Redis" version="3.9.45"
    targetFramework="net45" />
  <package id="ServiceStack.Text" version="3.9.46"
    targetFramework="net45" />
</packages>

Затем преобразуем контракты данных WCF, определенные в проекте TicketSystem.ServiceContract, в нечто понятное ServiceStack.

Преобразование контрактов данных WCF в контракты данных ServiceStack WCF использует контракты данных для установления средств взаимодействия между клиентом и сервером. ServiceStack делает то же самое. WCF требует, чтобы любые объекты данных и элементы данных, которые вы хотите сериализовать и отправлять по сети, помечались соответствующими атрибутами; в ином случае WCF просто игнорирует их. И здесь проявляется отличие ServiceStack от WCF. ServiceStack будет сериализовать все Plain Old CLR Objects (POCO), на которые есть ссылки в контракте сервиса, и сделает их видимыми на клиентской стороне. Ниже даны WCF- и ServiceStack-представления кода одного и того контракта данных Ticket в интерфейсе TicketService**.**

Вот контракт данных WCF <Ticket>:

[DataContract]
public class Ticket {
  [DataMember]
  public int TicketId { get; set; }
  [DataMember]
  public int TableNumber { get; set; }
  [DataMember]
  public int ServerId { get; set; }
  [DataMember]
  public List<Order> Orders { get; set; }
  [DataMember]
  public DateTime Timestamp { get; set; }
}

А это контракт данных ServiceStack <Ticket>:

public class Ticket {
  public int TicketId { get; set; }
  public int TableNumber { get; set; }
  public int ServerId { get; set; }
  public List<Order> Orders { get; set; }
  public DateTime Timestamp { get; set; }
}

Основное различие между ServiceStack и WCF в отношении интерфейса сервиса заключается в том, что ServiceStack налагает дополнительные ограничения на этот интерфейс. ServiceStack требует, чтобы каждый уникальный запрос идентифицировался уникальным объектом запроса, потому что в мире ServiceStack нет концепции операции веб-сервиса (т. е. имени метода). А значит, повторно использовать DTO запроса в нескольких реализациях сервиса на основе ServiceStack нельзя. Все контракты операций веб-сервиса на основе ServiceStack можно было бы приравнять к чему-то похожему в следующих контрактах сервиса WCF:

[ServiceContract]
public interface ITicketService {
  [OperationContract]
  List<Ticket> GetAllTicketsInQueue();
  [OperationContract]
  void QueueTicket(Ticket ticket);
  [OperationContract]
  Ticket PullTicket();
}

Предыдущий код — это не более чем исходный RPC-стиль интерфейса WCF-сервиса TicketService, показанного здесь, но преобразованного для поддержки соглашения DTO:

[ServiceContract]
public interface ITicketService {
  [OperationContract]
  List<Ticket> GetAllTicketsInQueue();
  [OperationContract]
  void QueueTicket(Ticket ticket);
  [OperationContract]
  Ticket PullTicket();
}

А это эквивалентный интерфейс сервиса TicketService на основе ServiceStack:

public class GetAllTicketsInQueueRequest {}
public class QueueTicketRequest {
  public Ticket Ticket { get; set; }
}
public class PullTicketRequest {}
public interface ITicketService {
  List<Ticket> Any(GetAllTicketsInQueueRequest request);
  void Any(QueueTicketRequest request);
  Ticket Any(PullTicketRequest request);
}

ServiceStack поддерживает различные действия, такие как Any, Get и Post. Их выбор влияет лишь на HTTP-запросы. Задание Any в запросе веб-сервиса означает, что операция может быть вызвана как HTTP GET, так и HTTP POST. Это упрощает RESTful-реализацию веб-сервиса, но ее обсуждение выходит за рамки данной статьи. Чтобы преобразовать веб-сервис на основе ServiceStack в эквивалент на основе RESTful, просто добавьте атрибуты URL [Route(…)] в объявления запросов веб-сервиса.

Создание веб-сервиса на основе ServiceStack, размещенного в ASP.NET Теперь, когда у вас определен интерфейс веб-сервиса на основе ServiceStack, пора переходить к его реализации и запуску.

Сначала добавьте проект ASP.NET Empty Web Application. Так удобнее всего выбрать хостинг веб-сервиса на основе ServiceStack. Это отнюдь не единственный способ хостинга веб-сервиса, созданного с помощью ServiceStack. Такой сервис можно разместить внутри Windows-службы или в форме консольного приложения, выполняемого на веб-сервере, или даже сделать его независимым.

Этот проект Visual Studio я назвал ServiceStackServer. Он эквивалентен проекту WcfServer в WCF-версии примера сервиса.

Для добавления ссылок ServiceStack в ServiceStackServer используйте NuGet Package Manager Console (рис. 6).

Добавление ссылок на библиотеки ServiceStack в проект ServiceStackServer
Рис. 6. Добавление ссылок на библиотеки ServiceStack в проект ServiceStackServer

Теперь у вас есть все, что нужно для реализации интерфейса ITicketService. Класс TicketService должен расширять класс ServiceStack.ServiceInterface.Service, предоставляемый инфраструктурой:

public class TicketService : ServiceStack.ServiceInterface.Service,
 ITicketService {
  public List<Ticket> Any(GetAllTicketsInQueueRequest request) {
    // Реализация...
  }
  public void Any(QueueTicketRequest request) {
    // Реализация...
  }
  public Ticket Any(PullTicketRequest request) {
    // Реализация...
  }
}

Все остальное точно такое же, как и в WCF-реализации.

Затем добавьте Global Application Class с именем Global.asax в проект ServiceStackServer, как показано на рис. 7. Здесь выполняется инициализация вашего веб-приложения ASP.NET.

Добавление Global.asax в ServiceStackServer
Рис. 7. Добавление Global.asax в ServiceStackServer

Вы должны наследовать от класса ServiceStack.WebHost.Endpoints.AppHostBase, если хотите разместить свой веб-сервис в приложении ASP.NET. Пример см. на рис. 8.

Рис. 8. Размещение веб-сервиса на основе ServiceStack в ASP.NET

public class Global : System.Web.HttpApplication {
  public class TicketServiceHost :
    ServiceStack.WebHost.Endpoints.AppHostBase {
    // Регистрируем веб-сервис в ServiceStack
    public TicketServiceHost()
       : base("Ticket Service",typeof(TicketService).Assembly) {}
    public override void Configure(Funq.Container container) {
      // Регистрируем любые зависимости сервисов,
      // используемых здесь
    }
  }
  protected void Application_Start(object sender, EventArgs e) {
    // Инициализируем веб-сервис при запуске
    new TicketServiceHost().Init();
  }
}

При выполнении в IIS версий 7 и выше в ваш файл Web.config нужно добавить элемент configuration, показанный на рис. 9.

Рис. 9. Файл Web.config для IIS версий 7 и выше

<configuration>
  <system.web>...</system.web>
  <!-- Требуется для IIS 7 (и выше) -->
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <add path="*" name="ServiceStack.Factory"
        type="ServiceStack.WebHost.Endpoints.
          ServiceStackHttpHandlerFactory, ServiceStack"
        verb="*" preCondition="integratedMode"
          resourceType="Unspecified"
        allowPathInfo="true" />
    </handlers>
  </system.webServer>
</configuration>

При запуске вашего веб-приложения ServiceStack контракты сервиса перечисляются как метаданные операций (рис. 10). Теперь ваш веб-сервис готов принимать клиентские запросы. В следующем разделе я приведу некоторые примеры использования веб-сервиса на основе ServiceStack.

Метаданные TicketService
Рис. 10. Метаданные TicketService

Встроенные в ServiceStack клиенты

Группа разработки ServiceStack очень откровенно высказывается против использования генерируемого клиентского кода веб-сервиса, поэтому в данной инфраструктуре предоставляется набор встроенных клиентов в пространстве имен ServiceStack.ServiceClient.Web. Все встроенные клиенты реализуют ServiceStack.Service.IServiceClient. Те из них, которые поддерживают REST, реализуют ServiceStack.Service.IRestClient.

Вот список доступных клиентов:

  • JsonServiceClient;
  • JsvServiceClient;
  • XmlServiceClient;
  • MsgPackServiceClient;
  • ProtoBufServiceClient;
  • Soap11ServiceClient;
  • Soap12ServiceClient.

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

Чтобы не усложнять, создайте консольное приложение ServiceStackClient, которое будет потребителем сервиса ServiceStack TicketService и использовать JsvServiceClient. Если вы хотите опробовать другой клиент, просто замените JsvServiceClient в следующем коде на любой из доступных клиентов, перечисленных ранее:

static void Main(string[] args) {
  Console.Title = "ServiceStack Console Client";
  using (var client = new JsvServiceClient("http://localhost:30542")) {
    List<Ticket> queuedTickets = 
      client.Send<List<Ticket>>(new GetAllTicketsInQueueRequest());
    if (queuedTickets != null) {
      foreach (Ticket ticket in queuedTickets) {
        PrintTicket(ticket);
      }
    }
  }
}

Консольное приложение выполняет следующие операции:

  • запрашивает у TicketService все талоны в очереди;
  • выводит список талонов, полученных от TicketService.

Консольный вывод идентичен генерируемому WCF-клиентом, показанному на рис. 3.

Заключение

В этой статье я лишь поверхностно затронул те возможности, которые способен предложить ServiceStack пользователям WCF, нуждающимся в кросс-платформенных решениях. Я даже не обсуждал, например, поддержку в ServiceStack потоковой передачи, асинхронных запросов и очередей сообщений.

Как видите, в отличие от веб-сервиса на основе WCF для создания веб-сервиса на основе ServiceStack потребуется несколько дополнительных этапов. Однако я считаю эти усилия пренебрежимо малыми в сравнении с тем, что ваш уровень сервисов становится способным работать с несколькими платформами. Если бы я выбрал другой путь в поисках независимости от конкретной платформы, скажем, использование веб-сервиса на основе Java, усилий и затрат времени на обучение понадобилось бы куда больше.

С учетом всего сказанного, если ваш веб-сервис предназначен для выполнения только в Windows, то WCF, безусловно, является лучшим решением. При создании веб-сервиса на основе WCF с нуля в среде Windows издержки меньше, и вам не требуется сторонний редистрибутив для поддержки и развертывания в целевой системе.


Нян Ле (Ngan Le) — старший инженер по программному обеспечению, активно работал с WCF, а в последнее время начал исследовать ServiceStack как кросс-платформенное решение для веб-сервисов.

Выражаю благодарность за рецензирование статьи эксперту Microsoft Эндрю Оукли (Andrew Oakley).