Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Облако/Azure Службы разработки и развертывания для Windows Azure RSS

Службы разработки и развертывания для Windows Azure

Текущий рейтинг: 3 (проголосовало 1)
 Посетителей: 1009 | Просмотров: 1309 (сегодня 0)  Шрифт: - +

Windows Azure — новая платформа вычислений в облаке, разрабатываемая Microsoft (microsoft.com/windowsazure). Под вычислениями в облаке (cloud computing) подразумевается размещение приложений в виртуальной среде, доступной через Интернет. Эта среда прозрачно предоставляет аппаратное и программное обеспечение, сетевые средства и хранилище, необходимые приложению.

Как и другие среды вычислений в облаке, Windows Azure предоставляет среду хостинга приложений. Дополнительное преимущество Windows Azure заключается в том, что приложения для .NET Framework можно развертывать с минимальными изменениями.

Применение шаблонов архитектуры, ориентированной на сервисы (service-oriented architecture, SOA) и использование опыта, накопленного в реализации сервисно-ориентированных решений, являются ключевыми для успешного переноса ваших сервисов и приложений на новую платформу. Чтобы лучше понять, как применять шаблоны SOA при развертывании ПО в Windows Azure, рассмотрим ситуацию, в которой вымышленный банк переносит свои сервисы в облако.

Банковские операции в облаке

Woodgrove Bank — небольшое финансовое учреждение, где решили сосредоточиться на реализации новой инициативы под брендом Woodgrove Bank Online. Один из крупнейших клиентов Woodgrove Bank — Fourth Coffee — добровольно согласился опробовать новое решение для обработки транзакций по платежным картам. Подмножество сервисов, запланированных для этого решения, уже действует, и доступность этих сервисов повышает интерес других клиентов. Однако по мере развертывания решения возникают проблемы.

Первая проблема связана с масштабируемостью и надежностью. Woodgrove Bank никогда не хотел нести ответственность за хостинг своих ИТ-решений. Вместо этого он заключил соглашение с местных интернет-провайдером — Sesame Hosting Company. До сих пор Sesame Hosting полностью удовлетворял потребности Woodgrove Bank в веб-хостинге, но новое решение по обработке платежных карт предъявляет такие требования к масштабируемости, реализовать которые Sesame Hosting не готов.

Группа, отвечающая за технологические архитектуры в Woodgrove Bank, предложила избыточное развертывание сервисов Woodgrove Bank Online в соответствии с шаблоном Redundant Implementation (описания обсуждаемых в статье шаблонов см. на сайте soapatterns.org). По сути, этот шаблон предлагает подход, при котором сервисы намеренно развертываются с применением избыточности для большей масштабируемости и возможности восстановления после аварий. Компания Sesame Hosting изучила этот вариант, но не может позволить себе расширение своей инфраструктуры, которое сделало бы ее способной поддерживать избыточное развертывание сервисов. У этой компании просто нет ресурсов на закупку дополнительного оборудования (в том числе сетевого) и более высокие эксплуатационные затраты ПО.

График работ тоже создает проблему. Даже если бы Sesame Hosting смогла создать нужную инфраструктуру, она не уложилась бы во временные рамки, необходимые Woodgrove Bank для выполнения своего плана развертывания. Потребность только в найме и обучении персонала перечеркнула бы все планы Woodgrove Bank.

Осознав, что Sesame Hosting не в состоянии удовлетворить их потребности, группа из Woodgrove Bank начала прорабатывать вариант хостинга своих сервисов в общедоступном облаке. Платформа Windows Azure позволяет виртуализовать сервисы таким способом, который обеспечивает естественное применение шаблона Redundant Implementation. Эта функция Windows Azure называется On-Demand Application Instance (ее обсуждение см. в майском номере за 2009 г.). Эта функция и возможность использования информационных центров Microsoft без долгосрочных соглашений выглядела весьма привлекательной для группы из Woodgrove Bank. Рассмотрим подробнее, как Woodgrove Bank переносил свое решение на Windows Azure.

Основы развертывания

В первую очередь веб-сервис нужно развертывать, соблюдая принцип Standardized Service Contract. Группа использует утилиту WSCF.blue для генерации WCF-контрактов (Windows Communication Foundation) на основе WSDL- и XSD-файлов, настроенных на оптимальную совместимость. Контракты сервиса показаны на рис. 1.

Рис. 1 Начальные контракты сервиса

*

Поскольку со временем сервисы придется изменять и развивать, разработчики также разрешили реализовать в контрактах данных интерфейс IExtensibleObject для поддержки шаблона Forward Compatibility (рис. 2).

Рис. 2 Начальные контракты данных

*

Для хранения данных группа из Woodgrove Bank хочет использовать SQL Azure, так как этот продукт уже предлагает нужную структуру базы данных. Если бы разработчики могли обойтись без реляционного хранилища, они могли бы подумать о применении Windows Azure Storage.

Далее архитекторы Woodgrove Bank создают Visual Studio Template Cloud Service и используют Visual Studio для его публикации. Потом они входят на портал Windows Azure для создания их нового сервиса в облаке (рис. 3).

Рис. 3 Создание сервиса в Windows Azure Portal

*

После этого выводится экран, позволяющий начать развертывание сервиса. Нужно щелкнуть кнопку Deploy и указать пакет приложения, параметры конфигурации и название развертываемого сервиса. Еще несколько щелчков кнопок, и сервис размещен в облаке.

Пример конфигурации сервиса показан на рис. 4.

Рис. 4 Конфигурация сервиса для Windows Azure

<Role name="BankWebRole">
  <Instances count="1" />
  <ConfigurationSettings>
    <Setting
      name="DataConnectionString"
      value="DefaultEndpointsProtocol=https;AccountName=YOURACCOUNTNAME;AccountKey=YOURKEY" />
    <Setting
      name="DiagnosticsConnectionString"
      value="DefaultEndpointsProtocol=https;AccountName=YOURDIAGNOSTICSACCOUNTNAME;AccountKey=YOURKEY" />

Ключевой элемент конфигурации для достижения требований к масштабируемости, предъявляемых Woodgrove Bank:

<Instances count="1" />

Например, если разработчикам нужно 10 экземпляров, этот элемент настраивается так:

<Instances count="10" />

На рис. 5 показан экран, подтверждающий, что запускается и выполняется только один экземпляр. Кнопка Configure позволяет открыть экран, где можно отредактировать конфигурацию сервиса и изменить параметр Instances.

Рис. 5 Экземпляры, выполняемые в Windows Azure

*

Производительность и гибкость

После стресс-тестирования группа разработчиков из Woodgrove Bank обнаружила, что использование всего одного централизованного хранилища данных в SQL Azure приводит к постоянной деградации скорости отклика при повышенном трафике. Для устранения этой проблемы было решено задействовать хранилище таблиц Windows Azure, которое поддерживает более высокую масштабируемость за счет распределения разделов между множеством узлов. Хранилище таблиц Windows Azure также обеспечивает быстрый доступ к данным, поскольку система ведет мониторинг использования разделов и автоматически осуществляет балансировку нагрузки. Однако, поскольку это хранилище не является реляционным, группе пришлось проектировать некоторые новые структуры хранилища данных и выбрать комбинацию разбиения на разделы и поддержку ключей строк (row keys), которая обеспечивает хорошее время ответа.

В результате получилось три таблицы, как показано на рис. 6. UserAccountBalance хранит балансы по каждому счету, AccountTransactionLogg — все сообщения о транзакциях для конкретных счетов, а UserAccountTransaction — транзакции по счетам. Ключи разделов для таблиц UserAccountTransaction и AccountTransactionLogg формируются конкатенацией UserId и AccountId, так как они являются частью всех запросов, а это может ускорить время ответа. Ключ раздела для таблицы UserAccountBalance — UserId, а ключ строки — AccountId. Совместно они обеспечивают уникальную идентификацию пользователя и его счета.

Рис. 6 Модели хранилища таблиц в Windows Azure

*

Таким образом, на данный момент Woodgrove Bank считает проект успешным и хочет, чтобы это решение начали использовать больше клиентов. Вскоре находится такой клиент — World Wide Importers, но он предъявляет некоторые новые функциональные требования.

Требование, выглядящее самым важным, — изменение интерфейса сервиса (т. е. структуры информации). Согласно заявлению World Wide Importers, структура информации, используемая Woodgrove Bank, несовместима с их структурой. Ввиду важности этого требования для данного клиента, группа разработки из Woodgrove Bank предлагает применить шаблон Data Model Transformation. Разработчики могли бы создать несколько новых сервисов с интерфейсами, запрошенными World Wide Importers, и эти сервисы содержали бы логику преобразований моделей данных, используемых World Wide Importers и Woodgrove Bank.

С этой целью создается новая структура для UserAccount. Разработчики обеспечивают четкое сопоставление между классами UserAccountWwi и UserAccount, как показано на рис. 7.

Рис. 7 Структура UserAccount для преобразования моделей данных

*

Контракты сервисов нужны для приема специфического контракта данных (UserAccountWwi), который преобразует запросы в UserAccount до передачи вызовов другим частям решения, а затем преобразует их обратно в ответе. Архитекторы из Woodgrove Bank понимают, что они могли бы повторно использовать интерфейс базового сервиса при реализации новых требований. Окончательная архитектура представлена на рис. 8.

Рис. 8 Контракты сервисов для World Wide Importers

*

Разработчики предпочли реализовать преобразования данных с помощью пар методов расширения для класса UserAccount, в том числе методов TransformToUserAccountWwi и TransformToUserAccount.

Новый сервис принимает контракт данных UserAccountWwi. До передачи запросов другим уровням данные преобразуются в UserAccount вызовом метода расширения TransformToUserAccount, а до возврата ответа потребителю контракт UserAccount преобразуется обратно в UserAccountWwi вызовом TransformToUserAccountWwi. Подробнее об этих элементах решения см. исходный код UserAccountServiceAdvanced в комплекте, который можно скачать для этой статьи.

Сообщения и очереди

Хотя Woodgrove Bank теперь полностью подготовлен и может обслуживать большое количество входящих запросов, аналитики отметили значительные пиковые всплески в использовании сервиса. Некоторые из них происходят регулярно (а именно утром по понедельникам и после полудня по четвергам). Однако некоторые флуктуации совершенно непредсказуемы.

Добавление онлайновых ресурсов через конфигурацию Windows Azure было бы одним из самых простых решений, но теперь, когда в новых сервисах заинтересованы некоторые крупные клиенты вроде World Wide Importers, число флуктуаций в нагрузке на сервисы будет только увеличиваться.

Разработчики из Woodgrove Bank более внимательно изучили предложения для Windows Azure и обнаружили средства, которые позволили бы использовать шаблоны Reliable Messaging и Asynchronous Queuing. После анализа они пришли к выводу, что Reliable Messaging — не самый подходящий вариант, так как он ограничил бы свободу выбора технических решений для их клиентов. В то же время Asynchronous Queuing не требует никаких специальных технологий от клиентов, поэтому разработчики предпочли именно его. Однако в облаке Windows Azure шаблон Reliable Messaging имеет смысл, так как все применяемые при этом технологии предоставляются Microsoft.

Цель заключается в том, чтобы не терять ни одного сообщения, даже если сервисы отключены из-за какого-то сбоя или планового обслуживания. Шаблон Asynchronous Queuing как раз и решает такие проблемы, хотя некоторые предложения не годятся при использовании этого шаблона. Например, в случае онлайновых транзакций с платежными картами необходим запрос ответов с подтверждением или отклонением денежных переводов. Но в других ситуациях этот шаблон подходит как нельзя лучше.

Взаимодействие между ролями Web и Worker (пояснения этих ролей см. по ссылке msdn.microsoft.com/magazine/dd727504) осуществляется с помощью очередей Windows Azure (в ноябрьской CTP-версии возможно прямое взаимодействие между экземплярами ролей), которые по умолчанию являются асинхронными и отказоустойчивыми. Это не подразумевает автоматически, что взаимодействие между конечным пользователем и сервисами Woodgrove Bank тоже становится отказоустойчивым. По сути, коммуникационные линии между клиентом и сервисами, размещенными в роли Web, совершенно очевидно не являются надежными. В Woodgrove Bank решили не мучаться с этой проблемой, так как реализация механизмов отказоустойчивости на всем пути до клиентов на практике потребовала бы от этих клиентов использования тех же технологий, что и Woodgrove Bank. Это сочли нереалистичным и нежелательным.

Работа с очередями

Как только клиент посылает сообщение в UserAccountService, оно помещается в очередь Windows Azure, а клиент получает сообщение подтверждения. Затем UserAccountWorker сможет извлечь это сообщение из очереди. Если с UserAccountWorker произойдет сбой, сообщение не будет утрачено, так как оно надежно хранится в очереди.

Если обработка внутри UserAccountWorker вызывает ошибку, сообщение просто не удаляется из очереди. Чтобы гарантировать это, метод DeleteMessage очереди вызывается только после окончания всей работы. Если UserAccountWorker не закончил обработку сообщения до истечения периода ожидания (этот период "зашит" в код и составляет 20 секунд), сообщение вновь становится видимым в очереди, чтобы его мог попытаться обработать другой экземпляр UserAccountWorker.

Когда клиент посылает сообщение в UserAccountService, оно помещается в очередь и клиент получает сообщения подтверждения типа TransactionResponse. С точки зрения клиента, используется Asynchronous Queuing. Но для взаимодействия между UserAccountStorageAction и AccountStorageWorker, которые находятся соответственно в ролях Web и Worker, используется ReliableMessaging. Вот как обработчик вызова помещает сообщения в очередь:

public TransactionResponse ReliableInsertMoney(
  AccountTransactionRequest accountTransactionrequest) {

//last parameter (true) means that we want to serialize
//message to the queue as XML (serializeAsXml=true)
  return UserAccountHandler.ReliableInsertMoney(
    accounttransactionRequest.UserId,
    accounttransactionRequest.AccountId,
    accounttransactionRequest.Amount, true);
}

UserAccountHandler — это свойство, которое возвращает IUserAccountAction, вводимый в исполняющую среду. Это облегчает отделение реализации от контракта и последующие изменения в реализации:

public IUserAccountAction<Models.UserAccount> UserAccountHandler
  {get;set;}

public UserAccountService(
  IUserAccountAction<Models.UserAccount> action) {

  UserAccountHandler = action;
}

После отправки сообщения одной из ответственных операций оно помещается в очередь. Первый метод на рис. 9 показывает, как сохранить в очереди данные в виде сериализованного XML, а второй метод — как сохранить в ней данные в виде строки. Заметьте, что очереди Windows Azure накладывают ограничение: максимальный размер сообщения не должен превышать 8 Кб.

Рис. 9 Хранение данных

public TransactionResponse ReliableHandleMoneyInQueueAsXml(
  UserAccountTransaction accountTransaction){

  using (MemoryStream m = new MemoryStream()){
    XmlSerializer xs =
      new XmlSerializer(typeof(UserAccountTransaction));
    xs.Serialize(m, accountTransaction);

    try
    {
      QueueManager.AccountTransactionsQueue.AddMessage(
        new CloudQueueMessage(m.ToArray()));
      response.StatusForTransaction = TransactionStatus.Succeded;
    }
    catch(StorageClientException)
    {
      response.StatusForTransaction = TransactionStatus.Failed;
      response.Message =
        String.Format("Unable to insert message in the account transaction queue userId|AccountId={0}, messageId={1}",
        accountTransaction.PartitionKey, accountTransaction.RowKey);
    }
  }
  return response;
}

public TransactionResponse ReliableHandleMoneyInQueue(
  UserAccountTransaction accountTransaction){

  TransactionResponse response = this.CheckIfTransactionExists(
    accountTransaction.PartitionKey, accountTransaction.RowKey);

  if (response.StatusForTransaction == TransactionStatus.Proceed)
  {
    //userid|accountid is partkey
    //userid|accountid|transactionid|amount
    string msg = string.Format("{0}|{1}|{2}",
      accountTransaction.PartitionKey,
      accountTransaction.RowKey,
      accountTransaction.Amount);

    try
    {
      QueueManager.AccountTransactionsQueue.AddMessage(
        new CloudQueueMessage(msg));
      response.StatusForTransaction = TransactionStatus.Succeded;
    }
    catch(StorageClientException)
    {
      response.StatusForTransaction = TransactionStatus.Failed;
      response.Message =
        String.Format("Unable to insert message in the account transaction queue userId|AccountId={0}, messageId={1}",
        accountTransaction.PartitionKey, accountTransaction.RowKey);
    }
  }
  return response;
}

Класс QueueManager будет инициализировать очереди, используя определения из конфигурации:

CloudQueueClient queueClient =
  CloudStorageAccount.FromConfigurationSetting(
    "DataConnectionString").CreateCloudQueueClient();

accountTransQueue = queueClient.GetQueueReference(
  Helpers.Queues.AccountTransactionsQueue);
accountTransQueue.CreateIfNotExist();

loggQueue = queueClient.GetQueueReference(
  Helpers.Queues.AccountTransactionLoggQueue);
loggQueue.CreateIfNotExist();

AccountStorageWorker прослушивает сообщения в AccountTransactionQueue и извлекает их из очереди. Для прослушивания сообщений рабочий поток должен открыть правильную очередь:

var storageAccount = CloudStorageAccount.FromConfigurationSetting(
  "DataConnectionString");
// initialize queue storage
CloudQueueClient queueStorage = storageAccount.CreateCloudQueueClient();
accountTransactionQueue = queueStorage.GetQueueReference(
  Helpers.Queues.AccountTransactionsQueue);

После того как очередь открывается и AccountStorageWorker считывает сообщение, это сообщение на 20 секунд становится невидимым в очереди (тайм-аут видимости жестко задан в 20). В течение этого времени рабочий поток должен успеть обработать данное сообщение.

Если обработка заканчивается успешно, сообщение удаляется из очереди. В ином случае сообщение вновь помещается в очередь.

Обработка сообщений

Методу ProcessMessage нужно сначала получить содержимое сообщения. Это делается одним из двух способов. В первом случае сообщение должно храниться в очереди как строка:

//userid|accountid|transactionid|amount
var str = msg.AsString.Split('|');...

а во втором — как сериализованный XML:

using (MemoryStream m =
  new MemoryStream(msg.AsBytes)) {

  if (m != null) {
    XmlSerializer xs = new XmlSerializer(
      typeof(Core.TableStorage.UserAccountTransaction));
    var t = xs.Deserialize(m) as
      Core.TableStorage.UserAccountTransaction;

    if (t != null) { ....... }
  }
}

Если с UserAccountWorker произойдет сбой, сообщение не будет утрачено, так как оно надежно хранится в очереди. А если обработка внутри UserAccountWorker вызывает ошибку, сообщение не удаляется из очереди и вновь становится видимым в очереди через 20 секунд.

Чтобы гарантировать это, метод DeleteMessage очереди вызывается только после окончания всей работы. Если AccountStorageWorker не закончил обработку сообщения до истечения периода ожидания, оно вновь становится видимым в очереди, чтобы его мог попытаться обработать другой экземпляр AccountStorageWorker. На рис. 10 код имеет дело с сообщением, сохраненным в виде строки.

Рис. 10 Обработка сообщений в очереди

if (str.Length == 4){
  //userid|accountid|transactionid|amount
  UserAccountSqlAzureAction ds = new UserAccountSqlAzureAction(
    new Core.DataAccess.UserAccountDB("ConnStr"));
  try
  {
    Trace.WriteLine(String.Format("About to insert data to DB:{0}", str),
      "Information");
    ds.UpdateUserAccountBalance(new Guid(str[0]), new Guid(str[1]),
      double.Parse(str[3]));
    Trace.WriteLine(msg.AsString, "Information");
    accountTransactionLoggQueue.DeleteMessage(msg);
    Trace.WriteLine(String.Format("Deleted:{0}", str), "Information");
  }
  catch (Exception ex)
  {
    Trace.WriteLine(String.Format(
      "fail to insert:{0}", str, ex.Message), "Error");
  }
}

Поддержка идемпотентных операций

А как быть, если один из клиентов Woodgrove Bank посылает запрос на перевод денег с одного счета на другой и это сообщение теряется? Если заказчик заново посылает сообщение, есть вероятность, что два или более запроса будут приняты сервисом и обработаны по отдельности.

В Woodgrove Bank заранее выявили эту ситуацию и для ее решения применили шаблон Idempotent Capability. Этот шаблон требует, чтобы функции или операции реализовались как безопасные при повторном выполнении. Если в двух словах, то решение, которое нужно реализовать Woodgrove Bank, требует, чтобы клиенты назначали каждому запросу уникальный идентификатор и в случае повторной попытки гарантировали отправку точно такого же сообщения с тем же уникальным идентификатором. Для этого уникальный идентификатор сохраняется в хранилище таблиц Windows Azure. Перед обработкой любого запроса нужно проверить, не было ли уже обработано сообщение с таким идентификатором. Если да, создается корректный ответ, но обработка нового запроса не осуществляется.

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

Группа из Woodgrove Bank обновила методы ReliableInsertMoney и ReliableWithDrawMoney в IUserAccountAction и их реализации, добавив идентификатор транзакции:

TransactionResponse ReliableInsertMoney(
  Guid userId, Guid accountId, Guid transactionId,
  double amount, bool serializeToQueue);

TransactionResponse ReliableWithDrawMoney(
  Guid userId, Guid accountId, Guid transactionId,
  double amount, bool serializeToQueue);

В таблицу UserAccountTransaction (Windows Azure Storage) был добавлен TransactionId как RowKey, чтобы у каждой операции вставки в таблицу был уникальный идентификатор транзакции.

Ответственность за передачу уникального идентификатора сообщения для каждой уникальной транзакции возлагается на клиента:

WcfClient.Using(new AccountServiceClient(), client =>{
  using (new OperationContextScope(client.InnerChannel))
  {
    OperationContext.Current.OutgoingMessageHeaders.MessageId =
      messageId;
    client.ReliableInsertMoney(new AccountTransactionRequest {
      UserId = userId, AccountId = accountId, Amount = 1000 });
  }
});

Вспомогательный класс, используемый здесь, можно найти по ссылке soamag.com/I32/0909-4.asp.

Определение IUserAccountService осталось прежним. Единственное изменение, необходимое для реализации этой функциональности, — считывание MessageId из заголовков входящих сообщений от клиентов и их использование при обработке (рис. 11).

Рис. 11 Получение идентификаторов сообщений

public TransactionResponse ReliableInsertMoney(
  AccountTransactionRequest accountTransactionrequest) {
  var messageId =
    OperationContext.Current.IncomingMessageHeaders.MessageId;
  Guid messageGuid = Guid.Empty;
  if (messageId.TryGetGuid(out messageGuid))
    //last parameter (true) means that we want to serialize
    //message to the queue as XML (serializeAsXml=true)
    return UserAccountHandler.ReliableInsertMoney(
      accounttransactionRequest.UserId,
      accounttransactionRequest.AccountId, messageId,
      accounttransactionRequest.Amount, true);
  else
    return new TransactionResponse { StatusForTransaction =
      Core.Types.TransactionStatus.Failed,
      Message = "MessageId invalid" };
}

Обновленный UserAccountAction теперь получает идентификатор транзакции для каждой идемпотентной операции. Когда сервис пытается обработать какую-либо идемпотентную операцию, он сначала проверяет, не существует ли такая транзакция в хранилище таблиц. Если она существует, сервис возвращает сообщение транзакции, хранящейся в таблице AccountTransactionLogg. Идентификатор транзакции сохраняется как RowKey в UserAccountTransaction. Чтобы найти правильный счет и соответствующего пользователя, сервис передает ключ раздела (userid|accountid). Если данный идентификатор транзакции не найден, сообщение помещается в очередь AccountTransactionsQueue для дальнейшей обработки:

public TransactionResponse ReliableHandleMoneyInQueueAsXml(
  UserAccountTransaction accountTransaction) {
  TransactionResponse response = this.CheckIfTransactionExists(
    accountTransaction.PartitionKey, accountTransaction.RowKey);
  if(response.StatusForTransaction == TransactionStatus.Proceed) {
    ...
  }
  return response;
}

Применение метода CheckIfTransactionExists (рис. 12) гарантирует, что транзакция обрабатывается только раз. Он пытается найти идентификатор транзакции для конкретного пользовательского счета. Если такой идентификатор есть, клиент получит сообщение ответа с результатом уже выполненной транзакции.

Рис. 12 Проверка состояния и идентификатора транзакции

private TransactionResponse CheckIfTransactionExists(
  string userIdAccountId, string transId) {

  TransactionResponse transactionResponse =
    new Core.Models.TransactionResponse();

  var transaction = this.TransactionExists(userIdAccountId, transId);
  if (transaction != null) {
    transactionResponse.Message =
      String.Format("messageId:{0}, Message={1}, ",
      transaction.RowKey, transaction.Message);
    transactionResponse.StatusForTransaction =
      TransactionStatus.Completed;
  }
  else
    transactionResponse.StatusForTransaction =
      TransactionStatus.Proceed;
  return transactionResponse;
}

private UserAccountTransaction TransactionExists(
  string userIdAccountId, string transId) {
  UserAccountTransaction userAccountTransaction = null;
  using (var db = new UserAccountDataContext()) {
    try {
      userAccountTransaction =
        db.UserAccountTransactionTable.Where(
        uac => uac.PartitionKey == userIdAccountId &&
        uac.RowKey == transId).FirstOrDefault();
      userAccountTransaction.Message = "Transaction Exists";
    }
    catch (DataServiceQueryException e) {
      HttpStatusCode s;
      if (TableStorageHelpers.EvaluateException(e, out s) &&
        s == HttpStatusCode.NotFound) {
        // this would mean the entity was not found
        userAccountTransaction = null;
      }
    }
  }
  return userAccountTransaction;
}

Интересная особенность CheckIfTransactionExists заключается в том, что, если нужные вам данные не найдены, Windows Azure Storage возвращает HTTP-код состояния 404 (поскольку используется интерфейс REST). Более того, если данные не найдены, клиентские сервисы ADO.NET (System.Data.Services.Client) генерируют исключение.

Автор: Томас Эрл, Арман Куртажик и Хербьерн Вилхелмсен  •  Иcточник: Журнал MSDN  •  Опубликована: 22.11.2010
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.