Modèles de nuages

Conception de services pour Windows Azure

Thomas Erl, Arman Kurtagic et Herbjörn Wilhelmsen

Télécharger l'exemple de code

Windows Azure est une nouvelle plateforme d'informatique en nuage en développement chez Microsoft (microsoft.com/windowsazure). L'informatique en nuage (« cloud computing ») permet aux développeurs d'héberger des applications dans un environnement virtuel accessible sur Internet. Cet environnement fournit en toute transparence le matériel, les logiciels, les réseaux et le stockage dont les applications ont besoin.

Comme d'autres environnements en nuage, Windows Azure offre un environnement hébergé pour les applications. L'avantage en plus de Windows Azure est que les applications .NET Framework peuvent être déployées avec un minimum de modifications comparé à leurs versions bureau.

L'application de modèles d'architecture orientée service (SOA, Service-Oriented Architecture) et l'utilisation des expériences recueillies lors de l'implémentation de solutions orientées service seront les clés du succès de la migration de vos services et applications vers le nouveau monde de l'informatique en nuage. Pour mieux comprendre comment les modèles SOA peuvent être appliqués aux déploiements Windows Azure, examinons un scénario dans lequel une banque fictive transfère ses services vers une informatique en nuage.

Services bancaires en nuage

Woodgrove Bank est une petite banque qui a décidé d'adopter une nouvelle initiative de services bancaires en ligne nommée Woodgrove Bank Online. L'un des principaux clients de Woodgrove Bank, Fourth Coffee, s'est engagé à tester la nouvelle solution de traitement des transactions effectuées par carte bancaire. Une partie des services programmés pour la solution est déjà en ligne et la disponibilité de ces services a généré un certain intérêt de la part d'autres clients. Cependant, des difficultés surgissent avec la planification du déploiement d'autres parties de la solution.

Le premier problème concerne l'évolutivité et la fiabilité de la solution. Woodgrove Bank ne souhaitait pas être responsable de l'hébergement de ses solutions informatiques. Elle a donc passé un accord de fourniture de services avec un fournisseur de services Internet local nommé Sesame Hosting. Jusqu'à présent, Sesame Hosting répondait aux besoins de Woodgrove Bank en matière d'hébergement Web. Toutefois, la nouvelle solution de traitement des transactions par carte bancaire a introduit des exigences d'évolutivité que Sesame Hosting n'est pas en mesure à gérer.

L'équipe chargée de l'architecture technologique de Woodgrove Bank suggère de déployer les services Woodgrove Bank Online de façon redondante, conformément au modèle d'implémentation redondante (les descriptions des modèles mentionnés dans cet article sont disponibles à l'adresse soapatterns.org). En bref, le modèle suggère une approche selon laquelle les services sont déployés de façon redondante intentionnellement, en vue d'améliorer l'évolutivité et le basculement. La société Sesame Hosting examine cette option, mais elle ne peut pas se permettre d'étendre son infrastructure pour prendre en charge des déploiements de services redondants. Elle ne dispose pas des ressources ni du budget nécessaires pour gérer l'augmentation des besoins en matériel, en entretien des logiciels d'exploitation et en équipements de réseau nécessaires.

Le délai imparti constitue également un problème. Même si Sesame Hosting pouvait mettre l'infrastructure requise à disposition de son client, elle ne pourrait pas le faire à temps pour le programme de déploiement de Woodgrove Bank. À elle seule, la nécessité d'employer et de former du personnel supplémentaire repousserait l'extension de l'infrastructure bien au-delà du calendrier de Woodgrove Bank.

Comprenant que Sesame Hosting ne serait pas en mesure de répondre à ses besoins, l'équipe de Woodgrove Bank s'est tournée vers l'option d'héberger ses services dans un nuage public. La plateforme Windows Azure offre une méthode pour virtualiser des services qui appliquent naturellement le modèle d'implémentation redondante. Cette fonctionnalité de Windows Azure se nomme Instance d'application sur demande (décrite dans l'édition de mai 2009). Cette fonctionnalité, alliée à la possibilité d'utiliser des centres de données Microsoft sans engagement à long terme, semble intéressante pour l'équipe de Woodgrove Bank. Examinons de plus près la façon dont Woodgrove Bank migre sa solution vers Windows Azure.

Bases du déploiement

La première tâche consiste à déployer un service Web en suivant une approche Contrat d'abord adoptant le principe du contrat de services standardisé (Standardized Service Contract). L'équipe utilise l'outil WSCF.blue pour générer des contrats WCF (Windows Communication Foundation) à partir de standards WSDL et XSD modélisés pour assurer une interopérabilité optimale. Les contrats de services sont illustrés à la figure 1.

Figure 1 Les contrats de services initiaux

image: The Initial Service Contracts

Étant donné que les services devront être modifiés et évoluer avec le temps, les développeurs décident également de laisser leurs contrats de données implémenter l'interface IExtensibleObject pour prendre en charge le modèle de compatibilité ascendante (voir figure 2).

Figure 2 Les contrats de données initiaux

image: The Initial Data Contracts

Pour stocker les données requises, l'équipe de Woodgrove Bank souhaite utiliser SQL Azure car elle a déjà une structure de base de données existante qu'elle veut conserver. Si les développeurs étaient en mesure d'utiliser une banque de données non relationnelle, ils pourraient envisager d'utiliser le stockage Windows Azure.

Les architectes de Woodgrove Bank créent un service en nuage de modèles Visual Studio et utilisent Visual Studio pour le publier. Ils se connectent au portail Windows Azure pour créer leur nouveau service en nuage (voir figure 3).

Figure 3 Création d'un service dans le portail Windows Azure

image: Creating a Service in the Windows Azure Portal

Ensuite, un écran s'affiche pour leur permettre de commencer à déployer le service. Ils cliquent sur le bouton Déployer et spécifient un package d'application, des paramètres de configuration et le nom du déploiement. Au bout de quelques clics supplémentaires, leur service est en nuage.

La figure 4 illustre un exemple de configuration du service.

Figure 4 Configuration d'un service 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" />

Pour rendre la solution élastique en termes d'exigences d'évolutivité de Woodgrove Bank, elle doit contenir l'élément de configuration suivant :

<Instances count="1" />

Par exemple, si les développeurs avaient besoin de 10 instances, cet élément serait défini comme suit :

<Instances count="10" />

La figure 5 illustre l'écran qui confirme qu'une seule instance est exécutée. Lorsque les développeurs cliquent sur le bouton Configurer, un écran s'affiche pour leur permettre de modifier la configuration du service et de modifier le paramètre Instances en fonction des besoins.

Figure 5 Instances exécutées dans Windows Azure

Performances et flexibilité

Après des tests de contrainte, l'équipe de développement de Woodgrove Bank a découvert que l'utilisation d'une seule banque de données centrale dans SQL Azure entraînait des temps de réponse plus lents en période de trafic plus dense. Les développeurs ont décidé de résoudre ce problème de performances en utilisant le stockage par table Windows Azure, qui est conçu pour améliorer l'évolutivité en distribuant les partitions sur un grand nombre de nœuds de stockage. Le stockage par table Windows Azure offre en outre un accès rapide aux données car le système analyse l'utilisation des partitions et effectue automatiquement l'équilibrage des charges. Toutefois, étant donné que le stockage par table Windows Azure n'est pas une banque de données relationnelle, l'équipe a dû créer de nouvelles structures de stockage des données et sélectionner une combinaison de clés de partition et de ligne qui offrent des temps de réponse adéquats.

Ils ont ainsi créé trois tables, comme illustré à la figure 6. La table UserAccountBalance sera utilisée pour stocker les soldes des comptes d'utilisateurs. AccountTransactionLogg stockera tous les messages de transaction pour des comptes spécifiques. UserAccountTransaction stockera les transactions des comptes. Les clés de partition pour les tables UserAccountTransaction et AccountTransactionLogg ont été créées en concaténant UserId et AccountId parce que ces éléments font partie de toutes les requêtes et peuvent donner des temps de réponse courts. La clé de partition pour la table UserAccountBalance est UserId et la clé de ligne est AccountId. Ensemble, elles fournissent une identification unique d'un utilisateur et de son compte.

Figure 6 Modèles de stockage par table Windows Azure

image: Windows Azure Table Storage Models

Woodgrove Bank considère le projet comme étant un succès jusque là et souhaite étendre la solution à d'autres clients. La société World Wide Importers se déclare bientôt prête à utiliser la solution, mais avec de nouvelles exigences propres en termes de fonctionnalités.

Leur requête principale concerne la modification de l'interface du service (ou structure d'information). Selon World Wide Importers, la structure d'information utilisée par Woodgrove Bank n'est pas compatible avec la leur. En raison de l'importance de ce client particulier, l'équipe de développement de Woodgrove Bank suggère d'appliquer le modèle de transformation du modèle de données. Les développeurs pourraient ainsi créer plusieurs nouveaux services avec les interfaces requises par World Wide Importers et ces services contiendraient une logique permettant de traduire les requêtes entre les modèles de données de World Wide Importers et ceux de Woodgrove Bank.

Pour répondre à cette exigence, une nouvelle structure est créée pour la classe UserAccount. Les développeurs veillent à assurer un mappage clair entre les classes UserAccountWwi et UserAccount, comme illustré à la figure 7.

Figure 7 Structure UserAccount pour la transformation du modèle de données

image: UserAccount Structure for Data Model Transformation

Les contrats de services doivent accepter un contrat de données spécifique (UserAccountWwi) qui transforme les requêtes vers UserAccount avant de transférer l'appel à d'autres parties de la solution, puis les retransforme dans la réponse. Les architectes de Woodgrove Bank réalisent qu'ils pourraient réutiliser une interface de service de base lors de l'implémentation de ces nouvelles exigences. La conception finale est illustrée à la figure 8.

Figure 8 Contrats de services pour World Wide Importers

image: Service Contracts for World Wide Importers

Les développeurs choisissent d'implémenter les transformations des données en créant des méthodes d'extension pour la classe UserAccount, notamment les méthodes TransformToUserAccountWwi et TransformToUserAccount.

Le nouveau service accepte le contrat de données UserAccountWwi. Avant d'envoyer des requêtes à d'autres couches, les données sont transformées vers UserAccount en appelant la méthode d'extension TransformToUserAccount. Avant de renvoyer une réponse au client, le contrat UserAccount est retransformé vers UserAccountWwi en appelant la méthode TransformToUserAccountWwi. Pour plus de détails sur ces éléments, consultez le code source pour UserAccountServiceAdvanced dans le téléchargement de code pour cet article.

Messagerie et files d'attente

Bien que le service de Woodgrove Bank soit maintenant disponible et en mesure de répondre à un grand nombre de requêtes entrantes, les analystes ont noté des pointes d'utilisation importantes pour ce service. Certaines de ces pointes se produisent régulièrement (plus précisément le lundi matin et le jeudi après-midi). Toutefois, certaines fluctuations ne sont pas prévisibles.

La mise à disposition de ressources supplémentaires en ligne via la configuration Windows Azure serait une solution facile, mais maintenant que des clients importants tels que World Wide Importers sont intéressés par les nouveaux services, les développeurs s'attendent à une augmentation des fluctuations d'utilisations concurrentes.

Les développeurs de Woodgrove Bank ont examiné en détail l'offre de Windows Azure et découvert des fonctionnalités permettant l'application des modèles de messagerie fiable et de files d'attente asynchrones. Ils ont conclu que le modèle de messagerie fiable n'était pas le choix le plus approprié, car il limiterait les choix techniques de leur client. Le modèle de files d'attente asynchrones, par contre, ne requiert aucune technologie spéciale de la part des clients et ils ont donc décidé d'adopter ce modèle. Au sein du nuage Windows Azure, toutefois, le modèle de messagerie fiable était un choix judicieux puisque toute la technologie utilisée était fournie par Microsoft.

L'objectif était qu'aucun message ne serait perdu même si les services sont hors connexion en raison de conditions d'erreurs ou de maintenance programmée. Le modèle de files d'attente asynchrones permet de garantir cela, bien que certaines offres ne sont pas appropriées pour ce modèle. Par exemple, le traitement de transactions par carte en ligne requiert des réponses rapides pour la validation ou le refus de transfert de fonds. Toutefois, dans d'autres situations, le modèle serait approprié.

La communication entre les rôles Web et Worker (voir msdn.microsoft.com/magazine/dd727504 pour une explication de ces rôles) est assurée via les files d'attente Windows Azure (à partir de la version CTP de novembre, il est possible de communiquer directement entre des instances de rôles), qui sont par défaut à la fois asynchrones et fiables. Cela ne signifie pas nécessairement que la communication entre l'utilisateur final et les services de Woodgrove Bank est fiable. En fait, les lignes de communication entre le client et les services résidant dans le rôle Web ne sont clairement pas fiables. L'équipe de Woodgrove Bank a décidé de ne pas chercher à résoudre ce problème car l'implémentation de mécanismes de fiabilité jusqu'aux clients nécessiterait en pratique que les clients adoptent les mêmes choix technologiques que Woodgrove Bank. Ceci a été considéré comme irréaliste et non désirable.

Mise en situation des files d'attente

Dès qu'un client envoie un message à UserAccountService, ce message est placé dans une file d'attente Windows Azure et le client reçoit un message de confirmation. UserAccountWorker est alors en mesure de récupérer le message de la file d'attente. En cas d'erreur au niveau de UserAccountWorker, le message ne sera pas perdu puisqu'il est stocké de façon sécurisée dans la file d'attente.

Si une erreur se produit lors du traitement à l'intérieur de UserAccountWorker, le message ne sera pas supprimé de la file d'attente. Pour garantir ceci, l'appel à la méthode DeleteMessage de la file d'attente est effectué uniquement à la fin du travail. Si UserAccountWorker n'a pas fini de traiter le message à la fin du délai d'expiration (le délai d'expiration est codé en dur sur 20 secondes), le message sera à nouveau rendu visible dans la file d'attente, de sorte qu'une autre instance de UserAccountWorker puisse tenter de le traiter.

Dès qu'un client envoie un message à UserAccountService, ce message est placé dans une file d'attente et le client reçoit un message de confirmation de type TransactionResponse. Du point de vue du client, le service de files d'attente asynchrones est utilisé. ReliableMessaging est utilisé pour communiquer entre UserAccountStorageAction et AccountStorageWorker, qui résident respectivement dans le rôle Web et le rôle Worker. Voici comment le gestionnaire des appels place les messages dans la file d'attente :

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 est une propriété qui retourne une action IUserAccountAction, qui est injectée à l'exécution. Ceci permet de séparer plus facilement l'implémentation du contrat, et de modifier l'implémentation ultérieurement :

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

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

  UserAccountHandler = action;
}

Une fois que le message est envoyé à l'une des actions responsables, il est placé dans la file d'attente. La première méthode de la figure 9 illustre le stockage des données en tant que XML sérialisable et la deuxième illustre le stockage des données en tant que chaîne dans la file d'attente. Notez que les files d'attente Windows Azure ont une limitation ; la taille de message maximale est de 8 Ko.

Figure 9 Stockage des données

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; 
}

La classe QueueManager initialise les files d'attente en utilisant les définitions de la configuration :

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

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

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

AccountStorageWorker est à l'écoute des messages sur AccountTransactionQueue et récupère les messages de la file d'attente. Pour être en mesure d'écouter le message, le rôle Worker doit ouvrir la file d'attente correcte :

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

Une fois que la file d'attente est ouverte et que AccountStorageWorker lit le message, celui-ci devient invisible dans la file d'attente pendant 20 secondes (le délai de visibilité a été défini sur 20). Pendant cette période, le rôle Worker tentera de traiter le message.

Si le traitement du message réussit, le message sera supprimé de la file d'attente. S'il échoue, le message sera replacé dans la file d'attente.

Traitement des messages

La méthode ProcessMessage doit tout d'abord récupérer le contenu du message. Ceci peut être effectué de deux façons. Premièrement, le message peut être stocké en tant que chaîne dans la file d'attente :

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

Deuxièmement, le message peut être au format XML sérialisé :

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) { ....... }
  }
}

En cas d'erreur quelconque au niveau du rôle AccountStorageWorker ou si celui-ci n'est pas en mesure de traiter le message, celui-ci ne sera pas perdu puisqu'il est enregistré dans la file d'attente. Si le traitement à l'intérieur du rôle AccountStorageWorker échoue, le message ne sera pas supprimé de la file d'attente et il deviendra visible dans la file d'attente après 20 secondes.

Pour garantir ce comportement, l'appel à la méthode DeleteMessage de la file d'attente est effectué uniquement à la fin du travail. Si AccountStorageWorker n'a pas fini de traiter le message à la fin du délai d'expiration, le message sera à nouveau rendu visible dans la file d'attente de sorte qu'une autre instance de AccountStorageWorker puisse tenter de le traiter. La figure 10 illustre un message stocké en tant que chaîne.

Figure 10 Gestion des messages en attente

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");
  }
}

Fonctionnalité idempotente

Que se passe-t-il si l'un des clients de Woodgrove Bank envoie une requête pour transférer des fonds d'un compte à un autre et que le message se perd ? Si le client renvoie le message, il est possible que deux ou plusieurs des requêtes atteignent les services et soient traitées séparément.

L'un des membres de l'équipe de Woodgrove Bank a immédiatement identifié ce scénario comme nécessitant l'implémentation du modèle de fonctionnalité idempotente. Ce modèle exige que des fonctionnalités ou opérations soient implémentées de sorte qu'elles puissent être répétées sans danger. En bref, la solution que Woodgrove Bank souhaite implémenter nécessite des clients qu'ils attachent un ID unique à chaque requête et promettent qu'ils renverront exactement le même message, y compris le même ID unique en cas de nouvelle tentative. Pour pouvoir gérer ceci, l'ID unique est enregistré dans le stockage par table Windows Azure. Avant de traiter des requêtes, il est nécessaire de vérifier si un message avec cet ID a déjà été traité. S'il a été traité, une réponse correcte sera créée, mais le traitement associé à la nouvelle requête n'aura pas lieu.

Bien que cela signifie que la banque de données centrale doit faire face à des requêtes supplémentaires, les développeurs ont considéré cette procédure comme nécessaire. Elle entraîne une certaine réduction des performances dans la mesure où certaines requêtes sont effectuées sur la banque de données centrale avant que tout autre traitement puisse avoir lieu. Toutefois, la consommation supplémentaire de temps et d'autres ressources associée est un choix raisonnable pour répondre aux besoins de Woodgrove Bank.

L'équipe de Woodgrove Bank a mis à jour les méthodes ReliableInsertMoney et ReliableWithDrawMoney dans IUserAccountAction et leurs implémentations en ajoutant un ID de transaction :

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

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

http://systemstatusx.lionbridge.com/systemstatus\_logoport/LP-Status.htm

La responsabilité pour l'envoi d'un ID unique de message pour chaque transaction est définie sur le client :

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 }); 
  } 
});

La classe d'assistance utilisée ici est disponible à l'adresse soamag.com/I32/0909-4.asp.

La définition IUserAccountService n'a pas été modifiée. La seule modification nécessaire pour implémenter cette fonctionnalité est de lire l'identificateur MessageId à partir des en-têtes du message entrant envoyé par le client et de l'utiliser pour le traitement en coulisses (voir Figure 11).

Figure 11 Capture des ID de messages

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" };      
}

L'action UserAccountAction mise à jour aura maintenant un ID de transaction pour chaque opération idempotente. Lorsque le service tente d'effectuer une opération idempotente, il vérifie si la transaction existe dans le stockage par table. Si la transaction existe, le service retourne le message de la transaction stocké dans la table AccountTransactionLogg. L'ID de transaction sera enregistré sous RowKey dans la table de stockage UserAccountTransaction. Pour trouver l'utilisateur et le compte corrects, le service envoie la clé de partition (userid|accountid). S'il ne trouve pas l'ID de transaction, le message est placé dans la file d'attente AccountTransactionsQueue pour traitement ultérieur :

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

La méthode CheckIfTransactionExists (voir Figure 12) est utilisée pour vérifier que la transaction n'a pas été traitée. Elle tente de trouver l'ID de transaction pour un compte d'utilisateur spécifique. Si elle ne trouve pas l'ID de transaction, le client reçoit un message de réponse contenant les détails de la transaction déjà effectuée.

Figure 12 Vérification de l'état et de l'ID de la transaction

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;
}

Une propriété intéressante de CheckIfTransactionExists est que si les données que vous recherchez ne sont pas trouvées, le stockage Windows Azure retourne un code d'état 404 HTTP (parce qu'il utilise une interface REST). En outre, si les données ne sont pas trouvées, les services clients ADO.NET (System.Data.Services.Client) génèrent une exception.

Plus d'informations

Pour plus d'informations sur l'implémentation de cette solution de validation technique, veuillez consulter le code source fourni disponible en ligne. Les descriptions des modèles SOA sont publiées à l'adresse soapatterns.org. Pour toute question, contactez herbjorn@wilhelmsen.se.

Arman Kurtagić* est un consultant spécialisé dans les nouvelles technologies Microsoft. Il travaille à Omegapoint, qui développe des solutions informatiques sécurisées métier. Il a occupé diverses fonctions, notamment celles de développeur, d'architecte, de mentor et d'entrepreneur, et a acquis son expérience professionnelle dans des secteurs tels que les finances, les jeux vidéo et les médias.*

Herbjörn Wilhelmsen* est un consultant chez Forefront Consulting Group, basé à Stockholm. Il est spécialisé en architecture orientée service et en architecture métier. Wilhelmsen est président du comité de révision des modèles SOA (SOA Patterns Review Committee) et dirige actuellement le groupe Business 2 IT dans le cadre de la section suédoise de l'IASA. Il est le co-auteur du livre*  SOA with .NET and Azure*, qui fait partie de la série *Prentice Hall Service-Oriented Computing Series from Thomas Erl.

Thomas Erl* est l'auteur d'ouvrages sur l'architecture SOA le plus vendu dans le monde et est l'éditeur de la série* Prentice Hall Service-Oriented Computing Series from Thomas Erl et du SOA Magazine. Il a fondé la société SOA Systems Inc. et créé le programme de certification professionnelle SOA SOASchool.com. Erl a en outre fondé le groupe de travail SOA Manifesto et est un intervenant et formateur régulier dans de nombreuses manifestations publiques et privées. Pour plus d'informations, consultez le site thomaserl.com.

Je remercie l'expert technique suivant d'avoir relu cet article : Steve Marx