Веб-сервисы в .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).
Рис. 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.
Рис. 3. Консольный WCF-клиент
Создание эквивалентного веб-сервиса на основе ServiceStack
Требования для работы с ServiceStack Чтобы работать с ServiceStack, нужно сначала скачать необходимые редистрибутивы. Самый простой способ сделать это — воспользоваться расширением NuGet для Visual Studio, которое позволяет легко устанавливать и обновлять сторонние библиотеки и инструменты. Вы можете скачать и установить клиент NuGet с nuget.codeplex.com. После установки NuGet вы должны увидеть в Visual Studio элементы меню, показанные на рис. 4.
Рис. 4. NuGet Package Manager Console в Visual Studio
Теперь мы пошагово пройдем всю процедуру создания веб-сервиса на основе ServiceStack, эквивалентного ранее созданному веб-сервису на основе WCF.
В качестве отправной точки возьмем WCF-версию примера веб-сервиса. Затем удалим из решения проекты WcfClient и WcfServer. Эти проекты специфичны для WCF, и они будут потом заменены проектами, совместимыми с ServiceStack.
В окне Package Manager Console выберите проект TicketSystem.ServiceContract. В командной строке введите install-package ServiceStack, как показано на рис. 5.
Рис. 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).
Рис. 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.
Рис. 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.
Рис. 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).