Partager via



Juillet 2015

Volume 30, numéro 7

Cutting Edge - CQRS et Applications basées sur les messages

Par Dino Esposito | Juillet 2015

Dino EspositoÀ la fin de la journée, de commandement et de responsabilité de requête ségrégation (CQRS) est conception de logiciels qui sépare le code et le code qui modifie l'état de cet État juste lectures. Cette séparation peut être logique et basé sur différentes couches. Il peut aussi être physique et impliquent des niveaux distincts. Il n'y a aucun manifeste ou philosophie branchée derrière CQRS. Le seul facteur réside dans sa simplicité de conception. Une conception simplifiée en ces jours fous de très grande complexité de l'entreprise est la seule façon sécuritaire afin d'assurer l'efficacité, l'optimisation et succès.

Mon dernier article (msdn.microsoft.com/magazine/mt147237) offre un point de vue de l'approche CQRS qui le rendent adapté à tout type d'application. Au moment où que vous envisagez d'utiliser une architecture CQRS avec commandes distinctes et les piles de la requête, vous commencez à penser des façons d'optimiser séparément chaque pile.

On n'est plus les contraintes du modèle qui rendent certaines opérations risquées, peu pratique ou peut-être tout simplement trop cher. La vision du système devient beaucoup plus pragmatique. Plus important encore, il arrive comme un processus naturel. Même certains concepts de création piloté par domaine tels que les agrégats ne cherchez si pénible. Même, ils trouvent leur place naturelle dans la conception. C'est la puissance d'une conception simplifiée.

Si vous êtes maintenant assez curieux CQRS pour lancer la recherche pour les études de cas et applications à proximité de votre entreprise, vous pouvez trouver que la plupart de la référence aux scénarios d'application qui utilisent des événements et des messages pour modéliser et implémenter la logique métier. Tandis que CQRS peuvent joyeusement payer les factures avec des applications beaucoup plus simples — ceux vous pourriez qualifier autrement simple CRUD apps — il brille vraiment dans des situations avec une plus grande complexité de l'entreprise. De là, vous pouvez déduire une plus grande complexité des règles métier et ayant une forte inclinaison pour changer.

Architecture basée sur des messages

Tout en observant le monde réel, vous verrez des actions dans les processus et les événements qui résultent de ces actions. Actions et événements transportant des données et parfois produire de nouvelles données, et c'est le point. C'est juste des données. Vous n'avez pas besoin nécessairement un modèle d'objet à part entière à l'appui de l'exécution de ces actions. Un modèle d'objet peut toujours être utile. Comme vous verrez dans un instant, cependant, c'est juste une autre option possible pour l'organisation logique métier.

Une architecture basée sur des messages est bénéfique car il simplifie considérablement la gestion complexe, complexe et en perpétuelle mutation des flux de travail. Ces types de workflows incluent les dépendances sur code hérité, les services extérieurs et les règles changeantes dynamiquement. Toutefois, construire une architecture basée sur des messages serait presque impossible en dehors du contexte du CQRS qui maintient les piles commande et requête soigneusement séparée. Par conséquent, vous pouvez utiliser l'architecture suivante pour la pile de commande unique.

Un message peut être une commande ou un événement. Dans le code, vous généralement définir une classe de base de Message et de celle, définir des classes de base supplémentaires pour les commandes et les événements, comme le montre Figure 1.

Figure 1 définition de la classe de Base de Message

public class Message
{
  public DateTime TimeStamp { get; proteted set; }
  public string SagaId { get; protected set; }
}
public class Command : Message
{
  public string Name { get; protected set; }
}
public class Event : Message
{
  // Any properties that may help retrieving
  // and persisting events.
}

D'un point de vue sémantique, commandes et événements sont légèrement différentes entités et des finalités différentes mais connexes. Un événement est presque identique à celui du Microsoft .NET Framework : une classe qui transporte les données et vous avertit lorsque quelque chose qui s'est produite. Une commande est une action effectuée sur le serveur principal qui a demandé à un utilisateur ou un autre composant du système. Événements et commandes suivent les conventions d'affectation de noms assez classique. Les commandes sont absolument comme SubmitOrderCommand, tandis que les événements sont passé comme OrderCreated.

En général, en cliquant sur n'importe quel élément de l'interface provient une commande. Une fois que le système reçoit la commande, il est originaire d'une tâche. La tâche peut être quelque chose d'un processus dynamique de longue durée, une seule action ou un flux de travail sans État. Un nom commun pour une telle tâche est saga.

Une tâche est mono-directionnelle, procède de la présentation vers le bas par le middleware et probablement finit par modifier l'état du système et stockage. Commandes ne généralement renvoyer les données à la présentation, sauf peut-être une forme rapide de commentaires tels que si l'opération s'est terminée avec succès ou les raisons il a échoué.

Actions de l'utilisateur explicite ne sont pas le seul moyen de commandes des déclencheurs. Vous pouvez également placer une commande avec des services autonomes qui interagissent de façon asynchrone avec le système. Penser à un scénario B2B, telles que l'expédition des produits, dans laquelle la communication entre partenaires se produit sur un service HTTP.

Événements dans une Architecture basée sur des messages

Donc les commandes proviennent des tâches et tâches consistent souvent à plusieurs étapes qui se combinent pour former un flux de travail. Souvent quand une étape donnée s'exécute, une notification des résultats devrait passer à d'autres composants pour travaux supplémentaires. La chaîne des tâches subordonnées, déclenchée par une commande peut être longue et complexe. Une architecture basée sur des messages est bénéfique, car il vous permet de modéliser les flux de travail en ce qui concerne les événements et les actions individuelles (provoquées par des commandes). En définissant des composants du gestionnaire de commandes et les événements subséquents, vous pouvez modéliser tout processus métier complexes.

Plus important encore, vous pouvez suivre une métaphore du travail proche de celui d'un organigramme classique. Cela a grandement simplifie la compréhension des règles et simplifie la communication avec des experts du domaine. En outre, le flux de travail qui en résulte est divisé en multiples gestionnaires plus petits, chaque exécution d'un petit pas. Chaque étape a également place async commandes et notifie les autres écouteurs d'événements.

Un des avantages majeurs de cette approche, c'est que la logique de l'application est facilement modifiable et extensible. Tout ce que vous faire est d'écrire de nouvelles pièces et les ajouter au système, et vous pouvez le faire avec une certitude absolue qu'ils n'affectent pas le code existant et workflows existants. Pour voir pourquoi cela est vrai et comment cela fonctionne vraiment, je vais passer en revue quelques-uns des détails d'implémentation de l'architecture basée sur des messages, y compris un nouvel élément d'infrastructure — le bus.

Bienvenue au Bus

Pour commencer, je vais regarder un composant du bus à la main. L'interface de base d'un autobus est résumée ici :

public interface IBus
{
  void Send<T>(T command) where T : Command;
  void RaiseEvent<T>(T theEvent) where T : Event;
  void RegisterSaga<T>() where T : Saga;
  void RegisterHandler<T>();
}

En règle générale, le bus est un singleton. Il reçoit les requêtes à exécuter des commandes et des notifications d'événements. Le bus ne fait réellement n'importe quel travail concret. Il sélectionne seulement un composant inscrit à traiter la commande ou de gérer l'événement. Le bus est titulaire d'une liste des processus d'affaires connus déclenchées par les événements et les commandes ou avancée de commandes supplémentaires.

Les processus qui gèrent les commandes et les événements associés sont généralement dénommés sagas. Lors de la configuration initiale d'autobus, vous inscrire composants gestionnaire et saga. Un gestionnaire est juste un type plus simple de la saga et représente une opération exceptionnelle. Lorsque cette opération est requise, il commence et se termine sans être enchaînée à d'autres événements ou en poussant les autres commandes au bus. Figure 2 cadeaux fait référence à une implémentation de classe de bus possible qui détient des sagas et des gestionnaires dans la mémoire.

Figure 2 exemple d'une implémentation de classe de Bus

public class InMemoryBus : IBus
{
  private static IDictionary<Type, Type> RegisteredSagas =
    new Dictionary<Type, Type>();
  private static IList<Type> RegisteredHandlers =
    new List<Type>();
  private static IDictionary<string, Saga> RunningSagas =
    new Dictionary<string, Saga>();
  void IBus.RegisterSaga<T>() 
  {
    var sagaType = typeof(T);
    var messageType = sagaType.GetInterfaces()
      .First(i => i.Name.StartsWith(typeof(IStartWith<>).Name))
      .GenericTypeArguments
      .First();
    RegisteredSagas.Add(messageType, sagaType);
  }
  void IBus.Send<T>(T message)
  {
    SendInternal(message);
  }
  void IBus.RegisterHandler<T>()
  {
    RegisteredHandlers.Add(typeof(T));
  }
  void IBus.RaiseEvent<T>(T theEvent) 
  {
    EventStore.Save(theEvent);
    SendInternal(theEvent);
  }
  void SendInternal<T>(T message) where T : Message
  {
    // Step 1: Launch sagas that start with given message
    // Step 2: Deliver message to all already running sagas that
    // match the ID (message contains a saga ID)
    // Step 3: Deliver message to registered handlers
  }
}

Lorsque vous envoyez une commande au bus, il passe par un processus en trois étapes. Tout d'abord, le bus vérifie la liste des sagas enregistrés pour voir s'il y a tout enregistrés sagas configurés pour démarrer à la réception de ce message. Dans l'affirmative, un nouveau volet de la saga est instancié, passé le message et ajouté à la liste des sagas en cours d'exécution. Enfin, le bus vérifie pour voir si il y a n'importe quel gestionnaire enregistré intéressés par le message.

Un événement passé au bus est traité comme une commande et acheminé vers les écouteurs inscrits. Le cas échéant pour le scénario d'entreprise, cependant, il peut enregistrer un événement à certains magasins de l'événement. Un magasin d'événement est un magasin de données ajout seul ordinaires qui suit tous les événements dans un système. À l'aide des événements enregistrés varie un peu. Vous pouvez enregistrer des événements uniquement à des fins de suivi ou l'utiliser comme source de données unique (événement sourcing). Vous pouvez même l'utiliser pour suivre l'histoire d'une entité de données tout en utilisant des bases de données classiques pour enregistrer l'état de la dernière entité.

Écriture d'un volet de la Saga

Une saga est un composant qui déclare les informations suivantes : une commande ou un événement qui démarre le processus de l'entreprise associée à la saga et la liste des commandes que la saga peut gérer la liste des événements qui intéressent la saga. Une classe saga implémente les interfaces à travers lequel il déclare les commandes et les événements qui présentent un intérêt. Les interfaces comme IStartWith et ICanHandle sont définis comme suit :

public interface IStartWith<T> where T : Message
{
  void Handle(T message);
}
public interface ICanHandle<T> where T : Message
{
  void Handle(T message);
}

Voici un exemple de la signature d'un exemple de classe de saga :

public class CheckoutSaga : Saga<CheckoutSagaData>,
       IStartWith<StartCheckoutCommand>,
       ICanHandle<PaymentCompletedEvent>,
       ICanHandle<PaymentDeniedEvent>,
       ICanHandle<DeliveryRequestRefusedEvent>,
       ICanHandle<DeliveryRequestApprovedEvent>
{
  ...
}

Dans ce cas, la saga représente le processus de commande d'une boutique en ligne. La saga commence lorsque l'utilisateur clique sur le bouton de paiement et de la couche applicative pousse la commande Checkout au bus. Le constructeur de la saga génère un ID unique, ce qui est nécessaire pour gérer des instances simultanées du même processus métier. Vous devriez être capable de gérer plusieurs sagas de caisse en cours d'exécution simultanément. L'ID peut être un GUID, une valeur unique envoyé avec la demande de commande ou même l'ID de session.

Pour une saga, manipulation d'une commande ou un événement consiste à avoir la méthode de la poignée sur les interfaces ICanHandle ou IStartWith, appelées à partir du composant du bus. Dans la méthode de la poignée, la saga effectue un calcul ou données d'accès. Il puis envoie une autre commande d'autres sagas écoute ou juste déclenche un événement comme une notification. Par exemple, imaginez le workflow checkout est comme indiqué dans Figure 3.

le Workflow Checkout
Figure 3 le Workflow Checkout

La saga effectue toutes les étapes jusqu'à acceptant le paiement. À ce moment-là, il pousse un Accept­commande paiement au bus pour le PaymentSaga de procéder. Le paiement­Saga se déroulera et déclencher un événement PaymentCompleted ou PaymentDenied. Ces événements seront encore gérés par le CheckoutSaga. Cette saga sera ensuite passer à l'étape de livraison avec une autre commande placée contre une autre saga interagissant avec le sous-système externe de la compagnie de transport partenaire.

La concaténation des commandes et des événements conserve la saga vivants jusqu'à ce qu'il atteigne la fin. À cet égard, vous pourriez penser d'une saga comme un flux de travail classique avec commençant et finissant points. Une autre chose à noter est qu'une saga est généralement persistante. Persistance est généralement géré par le bus. L'exemple de classe de Bus présentée ici ne supporte pas persistance. Un commercial tels que NServiceBus ou même un autobus libres comme les casse-têtes pourrait utiliser SQL Server. Persistance de se produire, vous devez donner un ID unique à chaque instance de saga.

Jaquette en haut

Pour les applications modernes être réellement efficaces, ils doivent être capables d'évoluer avec les besoins de l'entreprise. Une architecture basée sur des messages qui le rend incroyablement facile à étendre et modifier des flux de travail et de soutenir de nouveaux scénarios. Vous pouvez gérer les extensions dans un isolement total, tout ce qu'il faut, c'est ajouter une nouvelle saga ou un nouveau gestionnaire, enregistrant avec le bus au démarrage de l'application et laissez-la savent gérer uniquement les messages il faut gérer. Le nouveau composant sera automatiquement invoqué que lorsqu'il est temps et travaille côte à côte avec le reste du système. C'est facile, simple et efficace.


Dino Esposito est le co-auteur de « Microsoft .NET : Conception d'Applications pour l'entreprise » (Microsoft Press, 2014) et "programmation ASP.NET MVC 5" (Microsoft Press, 2014). Un spécialiste technique de Microsoft .NET Framework et les plates-formes Android à JetBrains et intervenant régulier dans les événements de l'industrie dans le monde entier, Esposito partage sa vision du logiciel à software2cents.wordpress.com et sur Twitter à twitter.com/despos.

Grâce à l'expert technique suivante pour avoir relu cet article : Jon Arne Saeteras